美文网首页
阅读《重学设计模式》笔记1 - 工厂模式

阅读《重学设计模式》笔记1 - 工厂模式

作者: 欢喜的看着书 | 来源:发表于2023-07-11 16:37 被阅读0次

    1、设计模式遵循六大原则:

    • 单一职责( ⼀个类和方法只做一件事 )
    • 里⽒氏替换( 多态,⼦类可扩展父类 )
    • 依赖 倒置( 细节依赖抽象,下层依赖上层 )
    • 接⼝隔离( 建⽴单⼀接⼝ )
    • 迪⽶特原则( 最少知道,降低耦合 )
    • 开闭 原则( 抽象架构,扩展实现 )

    2、设计模式可以笼统的分为三大类

    2.1 创建者模式

    这类模式提供创建对象的机制, 能够提升已有代码的灵活性和可复⽤用性。

    创建者模式

    2.2 结构型模式

    这类模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。


    2.3 ⾏为模式

    这类模式负责对象间的高效沟通和职责委派。


    行为模式

    3 工厂方法模式

    ⼯厂方法模式

    ⼯⼚模式⼜称⼯⼚⽅法模式,是⼀种创建型设计模式,其在⽗类中提供⼀个创建对象的⽅法, 允许⼦类
    决定实例化对象的类型。
    这种设计模式也是 Java 开发中最常⻅的⼀种模式,它的主要意图是定义⼀个创建对象的接⼝,让其⼦类⾃⼰决定实例化哪⼀个⼯⼚类,⼯⼚模式使其创建过程延迟到⼦类进⾏。
    简单说就是为了提供代码结构的扩展性,屏蔽每⼀个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调⽤即可,同时,这也是去掉众多 ifelse 的⽅式。当然这可能也有⼀些缺点,⽐如需要实现的类⾮常多,如何去维护,怎样减低开发成本。但这些问题都可以在后续的设计模式结合使⽤中,逐步降低。

    3.2、案例 - 模拟发奖多种商品

    为了可以让整个学习的案例更加贴近实际开发,这⾥模拟互联⽹中在营销场景下的业务。由于营销场景
    的复杂、多变、临时的特性,它所需要的设计需要更加深⼊,否则会经常⾯临各种紧急CRUD操作,从
    ⽽让代码结构混乱不堪,难以维护。
    在营销场景中经常会有某个⽤户做了⼀些操作;打卡、分享、留⾔、邀请注册等等,进⾏返利积分,最
    后通过积分在兑换商品,从⽽促活和拉新。
    那么在这⾥我们模拟积分兑换中的发放多种类型商品,假如现在我们有如下三种类型的商品接⼝;


    从以上接⼝来看有如下信息:

    • 三个接⼝返回类型不同,有对象类型、布尔类型、还有⼀个空类型。
    • ⼊参不同,发放优惠券需要仿᯿、兑换卡需要卡ID、实物商品需要发货位置(对象中含有)。
    • 另外可能会随着后续的业务的发展,会新增其他种商品类型。因为你所有的开发需求都是随着业务对市场的拓展⽽带来的。

    3.2.2 ⽤⼀坨坨代码实现

    如果不考虑任何扩展性,只为了尽快满⾜需求,那么对这么⼏种奖励发放只需使⽤ifelse语句判断,调⽤不同的接⼝即可满⾜需求。可能这也是⼀些刚⼊⻔编程的⼩伙伴,常⽤的⽅式。接下来我们就先按照这样的⽅式来实现业务的需求。
    1、工厂结构


    • ⼯程结构上⾮常简单,⼀个⼊参对象 AwardReq 、⼀个出参对象 AwardRes ,以及⼀个接⼝类PrizeController
    1. ifelse实现需求
    public class PrizeController {
    
        private Logger logger = LoggerFactory.getLogger(PrizeController.class);
    
        public AwardRes awardToUser(AwardReq req) {
            String reqJson = JSON.toJSONString(req);
            AwardRes awardRes = null;
            try {
                logger.info("奖品发放开始{}。req:{}", req.getuId(), reqJson);
                // 按照不同类型方法商品[1优惠券、2实物商品、3第三方兑换卡(爱奇艺)]
                if (req.getAwardType() == 1) {
                    CouponService couponService = new CouponService();
                    CouponResult couponResult = couponService.sendCoupon(req.getuId(), req.getAwardNumber(), req.getBizId());
                    if ("0000".equals(couponResult.getCode())) {
                        awardRes = new AwardRes("0000", "发放成功");
                    } else {
                        awardRes = new AwardRes("0001", couponResult.getInfo());
                    }
                } else if (req.getAwardType() == 2) {
                    GoodsService goodsService = new GoodsService();
                    DeliverReq deliverReq = new DeliverReq();
                    deliverReq.setUserName(queryUserName(req.getuId()));
                    deliverReq.setUserPhone(queryUserPhoneNumber(req.getuId()));
                    deliverReq.setSku(req.getAwardNumber());
                    deliverReq.setOrderId(req.getBizId());
                    deliverReq.setConsigneeUserName(req.getExtMap().get("consigneeUserName"));
                    deliverReq.setConsigneeUserPhone(req.getExtMap().get("consigneeUserPhone"));
                    deliverReq.setConsigneeUserAddress(req.getExtMap().get("consigneeUserAddress"));
                    Boolean isSuccess = goodsService.deliverGoods(deliverReq);
                    if (isSuccess) {
                        awardRes = new AwardRes("0000", "发放成功");
                    } else {
                        awardRes = new AwardRes("0001", "发放失败");
                    }
                } else if (req.getAwardType() == 3) {
                    String bindMobileNumber = queryUserPhoneNumber(req.getuId());
                    IQiYiCardService iQiYiCardService = new IQiYiCardService();
                    iQiYiCardService.grantToken(bindMobileNumber, req.getAwardNumber());
                    awardRes = new AwardRes("0000", "发放成功");
                }
                logger.info("奖品发放完成{}。", req.getuId());
            } catch (Exception e) {
                logger.error("奖品发放失败{}。req:{}", req.getuId(), reqJson, e);
                awardRes = new AwardRes("0001", e.getMessage());
            }
    
            return awardRes;
        }
    
        private String queryUserName(String uId) {
            return "花花";
        }
    
        private String queryUserPhoneNumber(String uId) {
            return "15200101232";
        }
    
    }
    
    • 如上就是使用 ifelse 非常直接的实现出来业务需求的一坨代码,如果仅从业务角度看,研发如期甚至提前实现了功能。
    • 那这样的代码目前来看并不会有什么问题,但如果在经过几次的迭代和拓展,接手这段代码的研发将十分痛苦。重构成本高需要理清之前每一个接口的使用,测试回归验证时间长,需要全部验证一次。这也就是很多人并不愿意接手别人的代码,如果接手了又被压榨开发时间。那么可想而知这样的 ifelse 还会继续增加。

    3.3、工厂模式优化代码

    接下来使用工厂方法模式来进行代码优化,也算是一次很小的重构。整理重构会你会发现代码结构清晰了、也具备了下次新增业务需求的扩展性。但在实际使用中还会对此进行完善,目前的只是抽离出最核心的部分。

    1. 工程结构

    itstack-demo-design-1-02
    └── src
        ├── main
        │   └── java
        │       └── org.itstack.demo.design
        │           ├── store    
        │           │   ├── impl
        │           │   │   ├── CardCommodityService.java
        │           │   │   ├── CouponCommodityService.java 
        │           │   │   └── GoodsCommodityService.java  
        │           │   └── ICommodity.java
        │           └── StoreFactory.java 
        └── test
             └── java
                 └── org.itstack.demo.design.test
                     └── ApiTest.java
    
    • 首先,从上面的工程结构中你是否一些感觉,比如;它看上去清晰了、这样分层可以更好扩展了、似乎可以想象到每一个类做了什么。
    • 如果还不能理解为什么这样修改,也没有关系。因为你是在通过这样的文章,来学习设计模式的魅力。并且再获取源码后,进行实际操作几次也就慢慢掌握了工厂模式的技巧。

    2. 代码实现

    2.1 定义发奖接口

    public interface ICommodity {
    
        void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;
    
    }
    
    • 所有的奖品无论是实物、虚拟还是第三方,都需要通过我们的程序实现此接口进行处理,以保证最终入参出参的统一性。
    • 接口的入参包括;用户ID奖品ID业务ID以及扩展字段用于处理发放实物商品时的收获地址。

    2.2 实现奖品发放接口

    优惠券

    public class CouponCommodityService implements ICommodity {
    
        private Logger logger = LoggerFactory.getLogger(CouponCommodityService.class);
    
        private CouponService couponService = new CouponService();
    
        public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
            CouponResult couponResult = couponService.sendCoupon(uId, commodityId, bizId);
            logger.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
            logger.info("测试结果[优惠券]:{}", JSON.toJSON(couponResult));
            if (!"0000".equals(couponResult.getCode())) throw new RuntimeException(couponResult.getInfo());
        }
    
    }
    

    实物商品

    public class GoodsCommodityService implements ICommodity {
    
        private Logger logger = LoggerFactory.getLogger(GoodsCommodityService.class);
    
        private GoodsService goodsService = new GoodsService();
    
        public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
            DeliverReq deliverReq = new DeliverReq();
            deliverReq.setUserName(queryUserName(uId));
            deliverReq.setUserPhone(queryUserPhoneNumber(uId));
            deliverReq.setSku(commodityId);
            deliverReq.setOrderId(bizId);
            deliverReq.setConsigneeUserName(extMap.get("consigneeUserName"));
            deliverReq.setConsigneeUserPhone(extMap.get("consigneeUserPhone"));
            deliverReq.setConsigneeUserAddress(extMap.get("consigneeUserAddress"));
    
            Boolean isSuccess = goodsService.deliverGoods(deliverReq);
    
            logger.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
            logger.info("测试结果[优惠券]:{}", isSuccess);
    
            if (!isSuccess) throw new RuntimeException("实物商品发放失败");
        }
    
        private String queryUserName(String uId) {
            return "花花";
        }
    
        private String queryUserPhoneNumber(String uId) {
            return "15200101232";
        }
    
    }
    

    第三方兑换卡

    public class CardCommodityService implements ICommodity {
    
        private Logger logger = LoggerFactory.getLogger(CardCommodityService.class);
    
        // 模拟注入
        private IQiYiCardService iQiYiCardService = new IQiYiCardService();
    
        public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
            String mobile = queryUserMobile(uId);
            iQiYiCardService.grantToken(mobile, bizId);
            logger.info("请求参数[爱奇艺兑换卡] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
            logger.info("测试结果[爱奇艺兑换卡]:success");
        }
    
        private String queryUserMobile(String uId) {
            return "15200101232";
        }
    
    }
    
    • 从上面可以看到每一种奖品的实现都包括在自己的类中,新增、修改或者删除都不会影响其他奖品功能的测试,降低回归测试的可能。
    • 后续在新增的奖品只需要按照此结构进行填充即可,非常易于维护和扩展。
    • 在统一了入参以及出参后,调用方不在需要关心奖品发放的内部逻辑,按照统一的方式即可处理。

    2.3 创建商店工厂

    public class StoreFactory {
    
        public ICommodity getCommodityService(Integer commodityType) {
            if (null == commodityType) return null;
            if (1 == commodityType) return new CouponCommodityService();
            if (2 == commodityType) return new GoodsCommodityService();
            if (3 == commodityType) return new CardCommodityService();
            throw new RuntimeException("不存在的商品服务类型");
        }
    
    }
    
    • 这里我们定义了一个商店的工厂类,在里面按照类型实现各种商品的服务。可以非常干净整洁的处理你的代码,后续新增的商品在这里扩展即可。如果你不喜欢if判断,也可以使用switch或者map配置结构,会让代码更加干净。
    • 另外很多代码检查软件和编码要求,不喜欢if语句后面不写扩展,这里是为了更加干净的向你体现逻辑。在实际的业务编码中可以添加括号。

    4、总结

    • 从上到下的优化来看,工厂方法模式并不复杂,甚至这样的开发结构在你有所理解后,会发现更加简单了。
    • 那么这样的开发的好处知道后,也可以总结出来它的优点;避免创建者与具体的产品逻辑耦合满足单一职责,每一个业务逻辑实现都在所属自己的类中完成满足开闭原则,无需更改使用调用方就可以在程序中引入新的产品类型。但这样也会带来一些问题,比如有非常多的奖品类型,那么实现的子类会极速扩张。因此也需要使用其他的模式进行优化。
    • 从案例入手看设计模式往往要比看理论学的更加容易,因为案例是缩短理论到上手的最佳方式,如果你已经有所收获,一定要去尝试实操。

    相关文章

      网友评论

          本文标题:阅读《重学设计模式》笔记1 - 工厂模式

          本文链接:https://www.haomeiwen.com/subject/wexuudtx.html