一、工厂模式

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

1.1 工厂模式介绍

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

1.2 工厂模式实现

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

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

AwardReqAwardResPrizeController

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

public class PrizeController {    private Logger logger = LoggerFactory.getLogger(PrizeController.class);    public AwardRes AwardToUser(AwardReq awardReq) {        String reqJson = JSON.toJSONString(awardReq);        AwardRes awardRes = null;        try {            logger.info("奖品发放开始{}。 awardReq:{}", awardReq.getuId(), reqJson);            if (awardReq.getAwardType() == 1) {                CouponService couponService = new CouponService();                CouponResult couponResult = couponService.sendCoupon(awardReq.getuId(), awardReq.getAwardNumber(), awardReq.getBizId());                if ("0000".equals(couponResult.getCode())) {                    awardRes = new AwardRes(0000, "发放成功");                } else {                    awardRes = new AwardRes(0001, "发送失败");                }            } else if (awardReq.getAwardType() == 2) {                GoodsService goodsService = new GoodsService();                DeliverReq deliverReq = new DeliverReq();                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"));                Boolean isSuccess = goodsService.deliverGoods(deliverReq);                if (isSuccess) {                    awardRes = new AwardRes(0000, "发放成功");                } else {                    awardRes = new AwardRes(0001, "发送失败");                }            } else {                IQiYiCardService iQiYiCardService = new IQiYiCardService();                iQiYiCardService.grantToken(queryUserPhoneNumber(awardReq.getuId()), awardReq.getAwardNumber());                awardRes = new AwardRes(0000, "发送成功");            }            logger.info("奖品发放完成{}。", awardReq.getuId());        } catch (Exception e) {            logger.error("奖品发放失败{}。req:{}", awardReq.getuId(), reqJson, e);            awardRes = new AwardRes(0001, e.getMessage());        }        return awardRes;    }

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

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

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

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

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("不存在的商品服务类型");    }}

二、模板模式(Template pattern)

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

2.1 模板模式介绍

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

  • AbstractClass:抽象类,在抽象类中定义了一系列基本操作,这些操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时在抽象类中实现了一个模板方法TemplateMethod(),用于定义一个算法的框架。
  • ConcreteClass:具体子类,实现抽象类中声明的抽象方法,并完成子类特定算法的步骤
  • Client:客户端,使用模板方法模式

2.2 模板模式实现

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

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

public abstract class NetMall {    String uId;   // 用户ID    String uPwd;  // 用户密码    public NetMall(String uId, String uPwd) {        this.uId = uId;        this.uPwd = uPwd;    }    // 1.模拟登录    protected abstract Boolean login(String uId, String uPwd);    // 2.爬虫提取商品信息(登录后的优惠价格)    protected abstract Map<String, String> reptile(String skuUrl);    // 3.生成商品海报信息    protected abstract String createBase64(Map<String, String> goodsInfo);        /**     * 生成商品推广海报     *     * @param skuUrl 商品地址(京东、淘宝、当当)     * @return 海报图片base64位信息     */    public String generateGoodsPoster(String skuUrl) {        if (!login(uId, uPwd)) return null;             // 1\. 验证登录        Map<String, String> reptile = reptile(skuUrl);  // 2\. 爬虫商品        return createBase64(reptile);                   // 3\. 组装海报    }}

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

public class JDNetMall extends NetMall {    public JDNetMall(String uId, String uPwd) {        super(uId, uPwd);    }    //1.模拟登录    public Boolean login(String uId, String uPwd) {        return true;    }    //2.网页爬取    public Map<String, String> reptile(String skuUrl) {        String str = HttpClient.doGet(skuUrl);        Pattern p9 = Pattern.compile("(?<=title\\>).*(?=</title)");        Matcher m9 = p9.matcher(str);        Map<String, String> map = new ConcurrentHashMap<String, String>();        if (m9.find()) {            map.put("name", m9.group());        }        map.put("price", "5999.00");        return map;    }    //3.生成海报    public String createBase64(Map<String, String> goodsInfo) {        BASE64Encoder encoder = new BASE64Encoder();        return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());    }}

最后进行测试:

@Testpublic void test_NetMall() {    NetMall netMall = new JDNetMall("ethan", "******");    String base64 = netMall.generateGoodsPoster("https://item.jd.com/100008348542.html");}

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

三、策略模式(Strategy Pattern)

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

3.1 策略模式介绍

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

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

注意,如果一个系统中的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。下面来看看对应的UML结构图:

  • Stategy:抽象策略结构,定义各种不同的算法实现接口,上下文Context通过这个接口调用不同算法
  • ConcreteStrategy1、ConcreteStrategy2:实现抽象策略定义的接口,提供具体的算法实现
  • Context:上下文类,也叫环境类,持有策略类的引用,是外界调用策略的接口

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策略实现该接口:

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

public class SnowFlake implements IIdGenerator {    private Snowflake snowflake;    @PostConstruct    public void init() {        //总共有5位,部署0~32台机器        long workerId;        try {            workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());        } catch (Exception e) {            workerId = NetUtil.getLocalhostStr().hashCode();        }        workerId = workerId >> 16 & 31;        long dataCenterId = 1L;        snowflake = IdUtil.createSnowflake(workerId, dataCenterId);    }    @Override    public long nextId() {        return snowflake.nextId();    }}

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

@Configurationpublic class IdContext {    @Bean    public Map<Constants.Ids, IIdGenerator> idGenerator(SnowFlake snowFlake, ShortCode shortCode, RandomNumeric randomNumeric) {        Map<Constants.Ids, IIdGenerator> idGeneratorMap = new HashMap<>(8);        idGeneratorMap.put(Constants.Ids.SnowFlake, snowFlake);        idGeneratorMap.put(Constants.Ids.ShortCode, shortCode);        idGeneratorMap.put(Constants.Ids.RandomNumeric, randomNumeric);        return idGeneratorMap;    }}

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

 @Test public void init() {     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嵌套的问题:

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-else语句:

public class StoreFactory {    /**设置策略Map**/    private static Map<Integer, ICommodity> strategyMap = Maps.newHashMap();    public static ICommodity getCommodityService(Integer commodityType) {        return strategyMap.get(commodityType);    }    /**提前将策略注入 strategyMap **/    public static void register(Integer commodityType, ICommodity iCommodity) {        if (0 == commodityType || null == iCommodity) {            return;        }        strategyMap.put(commodityType, iCommodity);    }}

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

public interface ICommodity extends InitializingBean {        void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap);}

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

@Componentpublic class GoodsCommodityService implements ICommodity {    private Logger logger = LoggerFactory.getLogger(GoodsCommodityService.class);    private GoodsService goodsService = new GoodsService();    @Override    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) {        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);        if (!isSuccess) {            throw new RuntimeException("实物商品发送失败");        }    }    private String queryUserName(String uId) {        return "ethan";    }    private String queryUserPhoneNumber(String uId) {        return "12312341234";    }    @Override    public void afterPropertiesSet() throws Exception {        StoreFactory.register(2, this);    }}

最后进行测试:

@SpringBootTestpublic class ApiTest {    private Logger logger = LoggerFactory.getLogger(ApiTest.class);    @Test    public void commodity_test() {        //1.优惠券        ICommodity commodityService = StoreFactory.getCommodityService(1);        commodityService.sendCommodity("10001", "sdfsfdsdfsdfs", "1212121212", null);        //2.实物商品        ICommodity commodityService1 = StoreFactory.getCommodityService(2);        Map<String, String> extMap = new HashMap<String, String>();        extMap.put("consigneeUserName", "ethan");        extMap.put("consigneeUserPhone", "12312341234");        extMap.put("consigneeUserAddress", "北京市 海淀区 xxx");        commodityService1.sendCommodity("10001", "sdfsfdsdfsdfs", "1212121212", extMap);        //3.第三方兑换卡        ICommodity commodityService2 = StoreFactory.getCommodityService(3);        commodityService2.sendCommodity("10001", "SSDIIUIUHJHJ","12312312312",null);    }}

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

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

public interface ICommodity extends InitializingBean {    void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap);}

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

public abstract class AbstractCommodity implements InitializingBean {    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) {        //不支持操作异常,继承的子类可以任意选择方法进行实现        throw new UnsupportedOperationException();    }    public String templateTest(String str) {        throw new UnsupportedOperationException();    }}

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

@Componentpublic class CouponCommodityService extends AbstractCommodity {    private Logger logger = LoggerFactory.getLogger(CouponCommodityService.class);    private CouponService couponService = new CouponService();    @Override    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) {        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 (couponResult.getCode() != 0000) {            throw new RuntimeException(couponResult.getInfo());        }    }    @Override    public void afterPropertiesSet() throws Exception {        StoreFactory.register(1, this);    }}JAVA 复制 全屏

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

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

拿捏大厂面试,设计模式学习笔记(二)工厂模式、模板模式和策略模式的混合使用相关推荐

  1. 快速搞定前端技术一面 匹配大厂面试要求学习笔记

    快速搞定前端技术一面 匹配大厂面试要求学习笔记 第1章 课程介绍[说说面试的那些事儿] 本章会出几个面试题,分析每道题目设计的知识点,然后总结出一个完整的知识体系.让我们开始 "题目-> ...

  2. 设计模式学习笔记——抽象工厂(Abstract Factory)模式

    设计模式学习笔记--抽象工厂(Abstract Factory)模式 @(设计模式)[设计模式, 设计模式中文名, 设计模式英文名] 设计模式学习笔记抽象工厂Abstract Factory模式 基本 ...

  3. HeadFir st 设计模式学习笔记21-- 解释者(Inter pr eter)模式拾零

    HeadFir st 设计模式学习笔记21-- 解释者(Inter pr eter)模式拾零 1.概述 这个模式是在不能叫做模式,它的作用是实现⼀种语言规范的解释器,比如罗马数字解释器. 2.实例 我 ...

  4. 设计模式学习笔记二:简单工厂模式

    含义: 从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一.简单工厂模式是由一个工厂对象决定创 ...

  5. 享学金三银四一线大厂面试专题学习笔记

    课程领取链接:https://pan.baidu.com/s/1_4PIUb-Yl68aTW9Bw95iJA 提取码:tnav 如果想系统的全面自学Java编程,可以提供以下几个方法: 1.学习Jav ...

  6. 设计模式学习笔记------简单工厂

    简单工厂 一.实例1 1 /** 2 * 定义接口 3 * @author abc 4 * 5 */ 6 public interface Api { 7 public void test1(); 8 ...

  7. 步步为营 .NET 设计模式学习笔记系列总结

    设计模式我从开篇到23种设计模式的讲解总共花了进两个月的时间,其间有很多读者给我提出了很好的建议,同时也指出了我的不足,对此我表示感谢,正是由于很多读者的支持我才能坚持的写到最后.在此表示我真诚的谢意 ...

  8. 《300分钟搞定算法面试》学习笔记

    之所以不在CSDN直接发,怕说广告直接封了,但是分享还是要说明出处的,感觉老师讲的很好 资料请加群 : 891555732 <300分钟搞定算法面试>学习笔记(一) ------ 常用数据 ...

  9. 设计模式学习笔记--访问者(Visitor)模式

    写在模式学习之前 什么是设计模式:在我们进行程序设计时,逐渐形成了一些典型问题和问题的解决方案,这就是软件模式:每一个模式描述了一个在我们程序设计中经常发生的问题,以及该问题的解决方案:当我们碰到模式 ...

  10. 设计模式学习笔记--享元(Flyweight)模式

    写在模式学习之前 什么是设计模式:在我们进行程序设计时,逐渐形成了一些典型问题和问题的解决方案,这就是软件模式:每一个模式描述了一个在我们程序设计中经常发生的问题,以及该问题的解决方案:当我们碰到模式 ...

最新文章

  1. 测试方案_何小伟:ABTest测试方案
  2. boost asio 性能与线程安全性
  3. 13道Python3实例
  4. php protobuf 性能,php中使用protobuffer
  5. 10个我最喜欢问程序员的面试问题
  6. 数据挖掘—Apriori算法(Java实现)
  7. linux域文件夹权限设置密码,如何配置Linux 文件权限(经典详细版本: rwxst)
  8. php 查找键名,array_key_exists()函数搜索数组键名步骤详解
  9. HLG 1334 最好的心情[lis 变形]
  10. html转word 图片丢失 java_Java 实现 Word 转 pdf 文档的工具来了
  11. 凸优化第七章统计估计 7.1 参数分布估计
  12. 2019年5月的Flag!
  13. 计量数据分析数据库-计量分析资料大全空间计量分析资料大全
  14. 华3交换机配置命令【系统归纳】【方便好查】
  15. 汽车智能化新赛道——CMS国标明年1月1日落地,供应链已提前布局
  16. oauth2单点登录总结
  17. 适用Macos的几款好用的人物照片编辑工具
  18. 解锁教爸妈使用智能手机的新方法!
  19. WebStorm--常用插件
  20. 淘宝搜索展现原理,店铺商品排名较低怎么办

热门文章

  1. Weak1 Chapter2 Homework
  2. 大物流行业未来发展格局研判
  3. Zigbee之旅(一):开天辟地
  4. oracle创建用户及修改密码
  5. python读取bmp图片_用Python读取bmp文件
  6. 布局约束——Fence,Region,Guide
  7. 41、labelme数据集转dota、rolabelimg以及rolabelimg转dota、labelme和labelimg数据集旋转水平镜像 垂直镜像 和水平垂直镜像
  8. 非线性方程求解:弦截法和抛物线法
  9. JavaSE笔记 类与对象
  10. Gumbel 分布介绍(耿贝尔分布)