工厂模式、模板模式和策略模式

简介: 工厂模式又叫做工厂方法模式,是一种创建型设计模式,一般是在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

一、工厂模式(Factory pattern)

工厂模式又叫做工厂方法模式,是一种创建型设计模式,一般是在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

1.1 工厂模式介绍

工厂模式是Java 中比较常见的一种设计模式,实现方法是定义一个统一创建对象的接口,让其子类自己决定去实例化那个工厂类,解决不同条件下创建不同实例的问题。工厂方法模式在实际使用时会和其他的设计模式一起结合,而不是单独使用。比如在Lottery 项目中奖品的发放就是工厂+模板+策略模式。

1.2 工厂模式实现

举个例子,比如要实现不同奖品的发放业务,有优惠券、实体商品和会员电子卡这些奖品,那么我们可以定义这三种类型奖品的接口:

从上表可以看出,不同的奖品有不同的返回类型需求,那么我们该如何处理这些数据,并对应返回呢?常规思路可以想到通过统一的入参AwardReq,出参AwardRes,外加上一个PrizeController来具体实现这些奖品的数据处理任务:

AwardReq

AwardRes

PrizeController

但是这样势必会造成PrizeController这个类中逻辑判断过多,后期如果要继续扩展奖品类型,是非常困难和麻烦的。比如可以看看PrizeController中的代码:

publicclassPrizeController {

   privateLoggerlogger=LoggerFactory.getLogger(PrizeController.class);

   publicAwardResAwardToUser(AwardReqawardReq) {

       StringreqJson=JSON.toJSONString(awardReq);

       AwardResawardRes=null;

       try {

           logger.info("奖品发放开始{}。 awardReq:{}", awardReq.getuId(), reqJson);

           if (awardReq.getAwardType() ==1) {

               CouponServicecouponService=newCouponService();

               CouponResultcouponResult=couponService.sendCoupon(awardReq.getuId(), awardReq.getAwardNumber(), awardReq.getBizId());

               if ("0000".equals(couponResult.getCode())) {

                   awardRes=newAwardRes(0000, "发放成功");

               } else {

                   awardRes=newAwardRes(0001, "发送失败");

               }

           } elseif (awardReq.getAwardType() ==2) {

               GoodsServicegoodsService=newGoodsService();

               DeliverReqdeliverReq=newDeliverReq();

               deliverReq.setUserName(queryUserName(awardReq.getuId()));

               deliverReq.setUserPhone(queryUserPhoneNumber(awardReq.getuId()));

               deliverReq.setSku(awardReq.getAwardNumber());

               deliverReq.setOrderId(awardReq.getBizId());

               deliverReq.setConsigneeUserName(awardReq.getExtMap().get("consigneeUserName"));

               deliverReq.setConsigneeUserPhone(awardReq.getExtMap().get("consigneeUserPhone"));

               deliverReq.setConsigneeUserAddress(awardReq.getExtMap().get("consigneeUserAddress"));

               BooleanisSuccess=goodsService.deliverGoods(deliverReq);

               if (isSuccess) {

                   awardRes=newAwardRes(0000, "发放成功");

               } else {

                   awardRes=newAwardRes(0001, "发送失败");

               }

           } else {

               IQiYiCardServiceiQiYiCardService=newIQiYiCardService();

               iQiYiCardService.grantToken(queryUserPhoneNumber(awardReq.getuId()), awardReq.getAwardNumber());

               awardRes=newAwardRes(0000, "发送成功");

           }

           logger.info("奖品发放完成{}。", awardReq.getuId());

       } catch (Exceptione) {

           logger.error("奖品发放失败{}。req:{}", awardReq.getuId(), reqJson, e);

           awardRes=newAwardRes(0001, e.getMessage());

       }

       returnawardRes;

   }

PrizeController的类中,我们发现使用了很多简单的if-else判断。而且整个代码看起来很长,对于后续迭代和扩展会造成很大的麻烦,因此在考虑设计模式的单一职责原则后,我们可以利用工厂模式对奖品处理返回阶段进行抽取,让每个业务逻辑在自己所属的类中完成。

首先,我们从业务逻辑中发现无论是那种奖品,都需要发送,因此可以提炼出统一的入参接口和发送方法:ICommoditysendCommodity(String uId, String awardId, String bizId, Map<String, String> extMap)入参内容包括用户Id,奖品Id,yewuId,扩展字段进行实现业务逻辑的统一,具体如下UML图

网络异常,图片无法展示
|

然后,我们可以在具体的奖品内部实现对应的逻辑。

最后创建奖品工厂StoreFactory,可以通过奖品类型判断来实现不同奖品的服务,如下所示:

publicclassStoreFactory {

   publicICommoditygetCommodityService(IntegercommodityType) {

       if (null==commodityType) {

           returnnull;

       }

       if (1==commodityType) {

           returnnewCouponCommodityService();

       }

       if (2==commodityType) {

           returnnewGoodsCommodityService();

       }

       if (3==commodityType) {

           returnnewCardCommodityService();

       }

       thrownewRuntimeException("不存在的商品服务类型");

   }

}

二、模板模式(Template pattern)

模板模式的核心就是:通过一个公开定义抽象类中的方法模板,让继承该抽象类的子类重写方法实现该模板。

2.1 模板模式介绍

定义一个操作的大致框架,然后将具体细节放在子类中实现。也就是通过在抽象类中定义模板方法,让继承该子类具体实现模板方法的细节。

2.2 模板模式实现

举个例子,在爬取不同网页资源并生成对应推广海报业务时,我们会有固定的步骤,如:模拟登录、爬取信息、生成海报。这个时候就可以将流程模板抽离出来,让对应子类去实现具体的步骤。比如爬取微信公众号、淘宝、京东、当当网的网页服务信息。

首先,定义一个抽象类NetMall,然后再在该类中定义对应的模拟登录login、爬取信息reptile、生成海报createBase的抽象方法让子类继承。具体代码如下所示:

publicabstractclassNetMall {

   StringuId;   // 用户ID

   StringuPwd;  // 用户密码

   publicNetMall(StringuId, StringuPwd) {

       this.uId=uId;

       this.uPwd=uPwd;

   }

   // 1.模拟登录

   protectedabstractBooleanlogin(StringuId, StringuPwd);

   // 2.爬虫提取商品信息(登录后的优惠价格)

   protectedabstractMap<String, String>reptile(StringskuUrl);

   // 3.生成商品海报信息

   protectedabstractStringcreateBase64(Map<String, String>goodsInfo);

   

   /**

    * 生成商品推广海报

    *

    * @param skuUrl 商品地址(京东、淘宝、当当)

    * @return 海报图片base64位信息

    */

   publicStringgenerateGoodsPoster(StringskuUrl) {

       if (!login(uId, uPwd)) returnnull;             // 1. 验证登录

       Map<String, String>reptile=reptile(skuUrl);  // 2. 爬虫商品

       returncreateBase64(reptile);                   // 3. 组装海报

   }

}

接下来以抓取京东网页信息为例实现具体步骤:

publicclassJDNetMallextendsNetMall {

   publicJDNetMall(StringuId, StringuPwd) {

       super(uId, uPwd);

   }

   //1.模拟登录

   publicBooleanlogin(StringuId, StringuPwd) {

       returntrue;

   }

   //2.网页爬取

   publicMap<String, String>reptile(StringskuUrl) {

       Stringstr=HttpClient.doGet(skuUrl);

       Patternp9=Pattern.compile("(?<=title\\>).*(?=</title)");

       Matcherm9=p9.matcher(str);

       Map<String, String>map=newConcurrentHashMap<String, String>();

       if (m9.find()) {

           map.put("name", m9.group());

       }

       map.put("price", "5999.00");

       returnmap;

   }

   //3.生成海报

   publicStringcreateBase64(Map<String, String>goodsInfo) {

       BASE64Encoderencoder=newBASE64Encoder();

       returnencoder.encode(JSON.toJSONString(goodsInfo).getBytes());

   }

}

最后进行测试:

@Test

publicvoidtest_NetMall() {

   NetMallnetMall=newJDNetMall("ethan", "******");

   Stringbase64=netMall.generateGoodsPoster("https://item.jd.com/100008348542.html");

}

模板模式主要是提取子类中的核心公共代码,让每个子类对应完成所需的内容即可。

三、策略模式(Strategy Pattern)

策略模式是一种行为类型模式,如果在一个系统中有许多类,而区分他们的只是它们的行为,这个时候就可以利用策略模式来进行切换。

3.1 策略模式介绍

在侧率模式中,我们创建表示各种策略的对象和一个行为随着侧率对象改变而改变的 context 对象。

比如诸葛亮的锦囊妙计,每一个锦囊都是一个策略。在业务逻辑中,我们一般是使用具有同类可替代的行为逻辑算法场景,比如,不同类型的交易方式(信用卡、支付宝、微信),生成唯一ID的策略(UUID、雪花算法、Leaf算法)等,我们都可以先用策略模式对其进行行为包装,然后提供给外界进行调用。

注意,如果一个系统中的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

3.2 策略模式实现

就拿生成唯一ID业务来举例子,比如在雪花算法提出之前,我们一般使用的是UUID 来确认唯一ID。但是如果需要有序的生成ID,这个时候就要考虑一下其他的生成方法,比如雪花、Leaf等算法了。

可能刚开始我们是直接写一个类,在类里面调用UUID算法来生成,但是需要调用其他方法时,我们就必须在这个类里面用if-else等逻辑判断,然后再转换成另外的算法中。这样的做法和前面提到的工厂模式一样,会提高类之间的耦合度。所以我们可以使用策略模式将这些策略抽离出来,单独实现,防止后期若需要扩展带来的混乱。

首先,定义一个ID生成的接口IIdGenerator

public interface IIdGenerator {

   /**

    * 获取ID, 目前有三种实现方式

    * 1.雪花算法,主要用于生成单号

    * 2.日期算法,用于生成活动标号类,特性是生成数字串较短,但是指定时间内不能生成太多

    * 3.随机算法,用于生成策略ID

    * @return ID 返回ID

    */

   long nextId();

}

让不同生成ID策略实现该接口:

网络异常,图片无法展示
|

下面是雪花算法的具体实现 :

publicclassSnowFlakeimplementsIIdGenerator {

   privateSnowflakesnowflake;

   @PostConstruct

   publicvoidinit() {

       //总共有5位,部署0~32台机器

       longworkerId;

       try {

           workerId=NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());

       } catch (Exceptione) {

           workerId=NetUtil.getLocalhostStr().hashCode();

       }

       workerId=workerId>>16&31;

       longdataCenterId=1L;

       snowflake=IdUtil.createSnowflake(workerId, dataCenterId);

   }

   @Override

   publiclongnextId() {

       returnsnowflake.nextId();

   }

}

其次还要定义一个ID策略控制类IdContext ,通过外部不同的策略,利用统一的方法执行ID策略计算,如下所示:

@Configuration

publicclassIdContext {

   @Bean

   publicMap<Constants.Ids, IIdGenerator>idGenerator(SnowFlakesnowFlake, ShortCodeshortCode, RandomNumericrandomNumeric) {

       Map<Constants.Ids, IIdGenerator>idGeneratorMap=newHashMap<>(8);

       idGeneratorMap.put(Constants.Ids.SnowFlake, snowFlake);

       idGeneratorMap.put(Constants.Ids.ShortCode, shortCode);

       idGeneratorMap.put(Constants.Ids.RandomNumeric, randomNumeric);

       returnidGeneratorMap;

   }

}

所以在最后测试时,直接调用idGeneratorMap就可以实现不同策略服务的调用:

@Test

publicvoidinit() {

    logger.info("雪花算法策略,生成ID: {}", idGeneratorMap.get(Constants.Ids.SnowFlake).nextId());

    logger.info("日期算法策略,生成ID: {}", idGeneratorMap.get(Constants.Ids.ShortCode).nextId());

    logger.info("随机算法策略,生成ID: {}", idGeneratorMap.get(Constants.Ids.RandomNumeric).nextId());

}

四、三种模式的混合使用

在实际业务开发中,一般是多种设计模式一起混合使用。而工厂模式和策略模式搭配使用就是为了消除if-else的嵌套,下面就结合工厂模式中的案例来介绍一下:

4.1 策略模式+工厂模式

在第一节中的工厂模式中,我们利用工厂实现不同类型的奖品发放,但是在StoreFactory中还是有if-else嵌套的问题:

publicclassStoreFactory {

   publicICommoditygetCommodityService(IntegercommodityType) {

       if (null==commodityType) {

           returnnull;

       }

       if (1==commodityType) {

           returnnewCouponCommodityService();

       }

       if (2==commodityType) {

           returnnewGoodsCommodityService();

       }

       if (3==commodityType) {

           returnnewCardCommodityService();

       }

       thrownewRuntimeException("不存在的商品

这个时候可以利用策略模式消除if-else语句:

publicclassStoreFactory {

   /**设置策略Map**/

   privatestaticMap<Integer, ICommodity>strategyMap=Maps.newHashMap();

   publicstaticICommoditygetCommodityService(IntegercommodityType) {

       returnstrategyMap.get(commodityType);

   }

   /**提前将策略注入 strategyMap **/

   publicstaticvoidregister(IntegercommodityType, ICommodityiCommodity) {

       if (0==commodityType||null==iCommodity) {

           return;

       }

       strategyMap.put(commodityType, iCommodity);

   }

}

在奖品接口中继承InitializingBean,便于注入策略strategyMap

publicinterfaceICommodityextendsInitializingBean {

   

   voidsendCommodity(StringuId, StringcommodityId, StringbizId, Map<String, String>extMap);

}

然后再具体策略实现上注入对应策略:

@Component

publicclassGoodsCommodityServiceimplementsICommodity {

   privateLoggerlogger=LoggerFactory.getLogger(GoodsCommodityService.class);

   privateGoodsServicegoodsService=newGoodsService();

   @Override

   publicvoidsendCommodity(StringuId, StringcommodityId, StringbizId, Map<String, String>extMap) {

       DeliverReqdeliverReq=newDeliverReq();

       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"));

       BooleanisSuccess=goodsService.deliverGoods(deliverReq);

       if (!isSuccess) {

           thrownewRuntimeException("实物商品发送失败");

       }

   }

   privateStringqueryUserName(StringuId) {

       return"ethan";

   }

   privateStringqueryUserPhoneNumber(StringuId) {

       return"12312341234";

   }

   @Override

   publicvoidafterPropertiesSet() throwsException {

       StoreFactory.register(2, this);

   }

}

最后进行测试:

@SpringBootTest

publicclassApiTest {

   privateLoggerlogger=LoggerFactory.getLogger(ApiTest.class);

   @Test

   publicvoidcommodity_test() {

       //1.优惠券

       ICommoditycommodityService=StoreFactory.getCommodityService(1);

       commodityService.sendCommodity("10001", "sdfsfdsdfsdfs", "1212121212", null);

       //2.实物商品

       ICommoditycommodityService1=StoreFactory.getCommodityService(2);

       Map<String, String>extMap=newHashMap<String, String>();

       extMap.put("consigneeUserName", "ethan");

       extMap.put("consigneeUserPhone", "12312341234");

       extMap.put("consigneeUserAddress", "北京市 海淀区 xxx");

       commodityService1.sendCommodity("10001", "sdfsfdsdfsdfs", "1212121212", extMap);

       //3.第三方兑换卡

       ICommoditycommodityService2=StoreFactory.getCommodityService(3);

       commodityService2.sendCommodity("10001", "SSDIIUIUHJHJ","12312312312",null);

   }

}

网络异常,图片无法展示
|

4.2 策略模式+工厂模式+模板模式

还是以之前的例子,上面我们已经用策略+工厂模式实现了业务,如何将模板模式也应用其中呢?我们先看看核心的ICommodity接口:

publicinterfaceICommodityextendsInitializingBean {

   voidsendCommodity(StringuId, StringcommodityId, StringbizId, Map<String, String>extMap);

}

在这个接口中,只有一个sendCommodity方法,那么如果在具体实现策略的类中,需要不同的实现方法,这个时候我们就可以利用模板模式的思路,将接口换成抽象类:

publicabstractclassAbstractCommodityimplementsInitializingBean {

   publicvoidsendCommodity(StringuId, StringcommodityId, StringbizId, Map<String, String>extMap) {

       //不支持操作异常,继承的子类可以任意选择方法进行实现

       thrownewUnsupportedOperationException();

   }

   publicStringtemplateTest(Stringstr) {

       thrownewUnsupportedOperationException();

   }

}

如上,继承的子类方法可以任意实现具体的策略,以优惠券为例:

@Component

publicclassCouponCommodityServiceextendsAbstractCommodity {

   privateLoggerlogger=LoggerFactory.getLogger(CouponCommodityService.class);

   privateCouponServicecouponService=newCouponService();

   @Override

   publicvoidsendCommodity(StringuId, StringcommodityId, StringbizId, Map<String, String>extMap) {

       CouponResultcouponResult=couponService.sendCoupon(uId, commodityId, bizId);

       logger.info("请求参数[优惠券] => uId: {} commodityId: {} bizId: {} extMap: {}", uId, commodityId, bizId, JSON.toJSON(extMap));

       logger.info("测试结果[优惠券]:{}", JSON.toJSON(couponResult));

       if (couponResult.getCode() !=0000) {

           thrownewRuntimeException(couponResult.getInfo());

       }

   }

   @Override

   publicvoidafterPropertiesSet() throwsException {

       StoreFactory.register(1, this);

   }

}

这样的好处在于,子类可以根据需求在抽象类中选择继承一些方法,从而实现对应需要的功能。

综上,在日常业务逻辑中对于设计模式的使用,并不是非得一定要代码中有设计模式才行,简单的逻辑就用if-else即可。如果有复杂的业务逻辑,而且也符合对应的设计模式,这样使用模式才能真正够提高代码的逻辑性和可扩展性。

相关文章
|
7月前
|
设计模式 uml 微服务
个人理解简单工厂模式和策略模式的区别
个人理解简单工厂模式和策略模式的区别
66 1
|
3月前
|
设计模式
设计模式-简单工厂和工厂方法
设计模式-简单工厂和工厂方法
|
4月前
|
设计模式 算法
设计模式思考,简单工厂模式和策略模式的区别?
设计模式思考,简单工厂模式和策略模式的区别?
|
10天前
|
设计模式 算法
模板模式
​ 如有错误或有补充,以及任何改进的意见,请留下您的高见 定义 模板模式是一种行为型设计模式,它在一个抽象类中公开定义了执行它的方法的方式/模板。子类可以按需重写方法实现,但调用将以抽象类中定义的方式进行。这种模式的核心思想是将一个算法的骨架定义在一个抽象类中,而将一些步骤延迟到子类中实现。 实现模板模式的步骤包括: 抽象出一个模板类,该类定义了执行算法的骨架。 抽象类中定义算法的骨架,骨架中的具体算法由子类实现。 子类按需重写方法实现,但调用将以抽象类中定义的方式进行。 使用final修饰模板方法,防止别人重写模板方法。 优点 提高了代码的可维护性和复用性。 减少了代码冗余,使算法的修
9 0
|
4月前
行为型 模板模式
行为型 模板模式
14 0
|
8月前
|
设计模式 算法 Java
模板模式【Java设计模式】
模板模式【Java设计模式】
31 0
|
10月前
|
设计模式 算法 Java
详解模板模式
1.概述 模板模式是一种常用的设计模式,它定义了一个操作中的算法的骨架,将某些步骤延迟到子类中实现。模板模式使得子类可以在不改变算法结构的情况下重新定义算法中的某些步骤。 模板模式中有两个角色:
107 0
|
设计模式 程序员
嘿!策略模式和工厂模式的区别你知道吗?
嘿!策略模式和工厂模式的区别你知道吗?
|
存储 设计模式 监控
|
设计模式 算法 uml
设计模式系列1 - 模板模式&策略模式
之前一直做业务,写代码基本也都是if...else,设计模式虽然很早就知道(研究生期间把4人帮的那本《设计模式》都看了3遍),但是没有实际去写这块代码,所有总有一种雾里探花的感觉,然后时间一长,有的模式就忘记了,真遇到代码需要去重构时,或者看别人代码,还得查一下资料,“哦,原来是用的这个设计模式,对上号了”。所以这次我打算结合具体的业务场景,将常用的设计模式全部整理出来,主要是不想再眼高手低,以后代码重构时,各种设计模式能信手拈来,那么我的目的就达成了哈。
247 0
设计模式系列1 - 模板模式&策略模式