跟着学设计模式 (1)— 工厂方法模式

前提

好看的代码千篇一律,恶心的程序升职加薪。
谁发明了设计模式? 设计模式的概念最早是由 克里斯托佛·亚历山大 在其著作 《建筑模式语言》 中多
次提出的。 本书介绍了城市设计的 “语言”,提供了253个描述城镇、住宅、花园、房间及⻄部构
造的模式, 此类 “语言” 的基本单元就是模式。后来, 埃里希·伽玛 、 约翰·弗利赛德斯 、 拉尔夫·约
翰逊 和 理查德·赫尔姆 这四位作者接受了模式的概念。 1994 年, 他们出版了 《设计模式: 可复用面
向对象软件的基础》 一书, 将设计模式的概念应用到程序开发领域中。
本设计模式专题系列开始,会带着你使得设计模式的思想去优化代码。从学习设计模式的心得
并融入给自己。当然这里还需要多加练习,一定是人车合一,才能站在设计模式的基础上构建出更加合
理的代码。

工厂方法模式介绍


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

实现模拟发奖多种商品为例


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

代码实现

1.工程结构

2. 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("consigneeUserAddr
                            ess"));
                            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.测试验证

 @Test
    public void test_awardToUser() {
        PrizeController prizeController = new PrizeController();
        System.out.println("\r\n模拟发放优惠券测试\r\n");
        // 模拟发放优惠券测试
        AwardReq req01 = new AwardReq();
        req01.setuId("10001");
        req01.setAwardType(1);
        req01.setAwardNumber("EGM1023938910232121323432");
        req01.setBizId("791098764902132");
        AwardRes awardRes01 = prizeController.awardToUser(req01);
        logger.info("请求参数:{}", JSON.toJSON(req01));
        logger.info("测试结果:{}", JSON.toJSON(awardRes01));
        System.out.println("\r\n模拟方法实物商品\r\n");
        // 模拟⽅法实物商品
        AwardReq req02 = new AwardReq();
        req02.setuId("10001");
        req02.setAwardType(2);
        req02.setAwardNumber("9820198721311");
        req02.setBizId("1023000020112221113");
        Map<String,String> extMap = new HashMap<String,String>();
        extMap.put("consigneeUserName", "可机");
        extMap.put("consigneeUserPhone", "15200292123");
        extMap.put("consigneeUserAddress", "福建省.厦门市.思明区.XX街道.XX
                区.#18-2109");
                req02.setExtMap(extMap);

        commodityService_2.sendCommodity("10001","9820198721311","102300002011222
                1113", extMap);
                AwardRes awardRes02 = prizeController.awardToUser(req02);
        logger.info("请求参数:{}", JSON.toJSON(req02));
        logger.info("测试结果:{}", JSON.toJSON(awardRes02));
        System.out.println("\r\n第三方兑换卡(爱奇艺)\r\n");
        AwardReq req03 = new AwardReq();
        req03.setuId("10001");
        req03.setAwardType(3);
        req03.setAwardNumber("AQY1xjkUodl8LO975GdfrYUio");
        AwardRes awardRes03 = prizeController.awardToUser(req03);
        logger.info("请求参数:{}", JSON.toJSON(req03));
        logger.info("测试结果:{}", JSON.toJSON(awardRes03));
    }

4.结果

模拟发放优惠券测试
22:17:55.668 [main] INFO o.i.demo.design.PrizeController - 奖品发放开始
10001。req: {"awardNumber":"EGM1023938910232121323432","awardType":1,"bizId":"79109876
4902132","uId":"10001"}
模拟发放优惠券⼀张:10001,EGM1023938910232121323432,791098764902132
22:17:55.671 [main] INFO o.i.demo.design.PrizeController - 奖品发放完成
1000122:17:55.673 [main] INFO org.itstack.demo.test.ApiTest - 请求参
数:{"uId":"10001","bizId":"791098764902132","awardNumber":"EGM102393891023
2121323432","awardType":1}
22:17:55.674 [main] INFO org.itstack.demo.test.ApiTest - 测试结
果:{"code":"0000","info":"发放成功"}
模拟⽅法实物商品
22:17:55.675 [main] INFO o.i.demo.design.PrizeController - 奖品发放开始
10001。req: {"awardNumber":"9820198721311","awardType":2,"bizId":"1023000020112221113"
,"extMap":{"consigneeUserName":"可机","consigneeUserPhone":"15200292123","consigneeUserAddress":"福建省.厦门市.思明区.XX街道.XX区.#18-2109"},"uId":"10001"}
模拟发货实物商品个:{"consigneeUserAddress":"福建省.厦门市.思明区.XX街道.XX区.#18-2109","consigneeUserName":"可机","consigneeUserPhone":"15200292123","orderId":"1023000020112221113","sk
u":"9820198721311","userName":"花花","userPhone":"15200101232"}
22:17:55.677 [main] INFO o.i.demo.design.PrizeController - 奖品发放完成
1000122:17:55.677 [main] INFO org.itstack.demo.test.ApiTest - 请求参
数:{"extMap":{"consigneeUserName":"谢⻜机","consigneeUserAddress":"福建省.厦门市.思明区.XX街道.XX区.#18-
2109","consigneeUserPhone":"15200292123"},"uId":"10001","bizId":"102300002
0112221113","awardNumber":"9820198721311","awardType":2}
22:17:55.677 [main] INFO org.itstack.demo.test.ApiTest - 测试结
果:{"code":"0000","info":"发放成功"}
第三方兑换卡(爱奇艺)
22:17:55.678 [main] INFO o.i.demo.design.PrizeController - 奖品发放开始
10001。req: {"awardNumber":"AQY1xjkUodl8LO975GdfrYUio","awardType":3,"uId":"10001"}
模拟发放爱奇艺会员卡一张:15200101232,AQY1xjkUodl8LO975GdfrYUio
22:17:55.678 [main] INFO o.i.demo.design.PrizeController - 奖品发放完成
1000122:17:55.678 [main] INFO org.itstack.demo.test.ApiTest - 请求参
数:{"uId":"10001","awardNumber":"AQY1xjkUodl8LO975GdfrYUio","awardType":3}
22:17:55.678 [main] INFO org.itstack.demo.test.ApiTest - 测试结
果:{"code":"0000","info":"发放成功"}
Process finished with exit code 0

工厂模式优化代码

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

1. 工程结构
在这里插入图片描述
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("不存在的商品服务类型");
        }
    }

3. 测试验证

	@Test
    public void test_commodity() throws Exception {
        StoreFactory storeFactory = new StoreFactory();
        // 1. 优惠券
        ICommodity commodityService_1 = storeFactory.getCommodityService(1);
        commodityService_1.sendCommodity("10001", "EGM1023938910232121323432",
                "791098764902132", null);
        // 2. 实物商品
        ICommodity commodityService_2 = storeFactory.getCommodityService(2);

        Map<String,String> extMap = new HashMap<String,String>();
        extMap.put("consigneeUserName", "可机");
        extMap.put("consigneeUserPhone", "15200292123");
        extMap.put("consigneeUserAddress", "XX省.XX市.XX区.XX街道.XX⼩ 区.#18-2109");
        commodityService_2.sendCommodity("10001","9820198721311","102300002011222 1113", extMap);
                // 3. 第三方兑换卡(爱奇艺)
         ICommodity commodityService_3 = storeFactory.getCommodityService(3);

        commodityService_3.sendCommodity("10001","AQY1xjkUodl8LO975GdfrYUio",null,null);
    }

从上到下的优化来看,工厂方法模式并不复杂,甚至这样的开发结构在你有所理解后,会发现更加简单了。
那么这样的开发的好处知道后,也可以总结出来它的优点; 避免创建者与具体的产品逻辑耦合 、 满足单一职责,每个业务逻辑实现都在所属自己的类中完成 、 满足开闭原则,无需更改使用调用方就可以在程序中引入新的产品类型 。

以上是跟着某大神学习的,如果有更好的可以提出大家互相学习,多多指教!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值