千千万万设计模式之工厂模式

本贴最后更新于 1771 天前,其中的信息可能已经时移世改

cover

对了,让厂子偷偷给你定制一个对象吧!乔碧萝怎么样?


本系列已经开源至 GitHub,repository 地址

最初只是为了做个人笔记,参考了前人的笔记和博客,在这里我用更接潮流、更接地气的例子来帮助加深理解记忆。

由于本人技术水平也有限,着重点在于思想的理解,若出现任何错误、不恰当内容,欢迎各位前来 issues 指正。

感谢任何分享、开源学习教程的前辈,正是有你们这一群乐于奉献的人才让整个生态变得生机勃勃、让这个行业日新月异。

设计模式的思想

新开个系列,把前人写的设计模式好好地学习钻研下,这里做点笔记。这里参考的博文地址

什么是设计模式?放在两年前的我,不但不了解它,也不会去重视它。我只在乎能够猥琐实现,程序运行不报错。但是在版本快速迭代、需求明天就改、框架稳定升级的现在,自己也写了不少代码,积累了一些经验和知识,在快速成长的过程中,愈发觉得优秀的开发工程师就是会比平庸的开发工程师在设计、建模的过程中去花更多时间去思考、去推理。其实我觉得我也算是考虑问题比较全面、比较细致的人了(大雾)。
这里又可以引申出面向对象和面向过程,优秀的开发工程师可以把面向过程的程序写得非常内聚、可扩展性好、具备一定的复用性;而平庸的程序员用面向对象的语言一样也能把程序写得松散随意、毫无抽象与建模、模块耦合严重、维护性差。而设计模式也是考究程序员对业务的建模能力,以及对架构的宏观掌握能力,面向对象来说,以对象模型为核心,丰富模型的内涵,扩展模型的外延,通过模型的行为组合去共同解决某一力问题,抽象的能力必不可少。
啥是面向对象?总结就是四大特性:封装、继承、多态、抽象。这里不细讲了,留到之后在总结一篇 post 吧。

血汗工厂的由来

大家在写代码的时候都知道要注意解耦、增加复用性,但是偶尔也许会 ctrl+cctrl+v 大法来覆写代码片,包括 Intellij IDEA 编辑器发现有重复代码片也有用黄色波浪线来提醒。然而网上的教程都是如何教你去掉该死的黄色波浪线,1️⃣0️⃣🐭 弟弟行为。实际上出现这种问题,你更应该去关注你的代码设计的是否合理,是否符合开闭原则,有共性的地方能否提取出来?
工厂模式一共有三种,其中 简单工厂模式 是比较特殊的实现,首先它违背了开闭原则,根据工厂类的静态方法通过 if...else... 或者 switch...case... 来判断分支,一旦需要增减改分支都要去改代码。
工厂模式是适用性最广也是应用最多的一种工厂模式,该模式强调,每一个对象都有一个对应管理的工厂(你的对象其实是工具人)。

工厂模式

抽象类的解释

一般我们需要对象时候,通常的做法是 new 一个对象,工厂模式则是强调由具体的工厂来生产一个对象给你使用。

首先最好解释一下 抽象类,我当初就是一直不太能理解,可能对于很多初学者来说也是。简单来说,抽象类像是一个模板,比如说 Apple 的 MacBook Pro 产品线,基本上历年迭代一次。而最新 2019 年 MacBook Pro 产品线中,包含着几款配置不同的产品。这些产品的屏幕、键盘、CPU、内存、金属一体外壳是抽象出来的共同特征,每一款具体的产品都离不开这些属性。而不同型号的产品又存在差异化和卖点,比如 15inch 和 13inch 的屏幕,低中高配的 CPU、显卡、存储,特有的 touch bar 和触控 ID 等等。

2019 MacBook Pro

为了方便举例,我们理想化的认为这些不同型号产品的诞生都是由一个 MacBook Pro 模具 从抽象到具体的过程,针对不同需求的人群差异化的结果。抽象类也是如此,它通过类的继承可以有不同版本的实现,不同版本都会做相应的增删改。

具体实现

什么时候用工厂模式比较好?它能解决什么问题?

其实我觉得就一句话,降低耦合度和批量化生产。

因为工厂模式是针对单一产品簇的对象,比如一类膨化食品、一类手机、一类 blazer。这些产品不去麻烦客户而交给工厂去处理,之后产品大面积出现问题返厂或者迭代更新也都是各个工厂的事。

这里有 4 个关于工厂模式的角色概念,我用下图表示了他们之间的关系。

  • 抽象工厂(Abstract Factory)角色:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
  • 具体工厂(Concrete Factory)角色:这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建某一种产品对象。
  • 抽象产品(AbstractProduct)角色:工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
  • 具体产品(Concrete Product)角色:这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。

本文实例

/**
 * @ClassName MacBookFactory
 * @Description MacBook的抽象工厂类
 * @Author MatthewHan
 * @Date 2019/8/1 18:01
 * @Version 1.0
 **/
public interface MacBookFactory {

    /**
     * MacBook抽象工厂
     * @return
     */
    AbstractMacBookProduct createMacBook();
}
MacBook的抽象工厂类,用于描述所有具体型号生产的MacBook工厂的抽象基类。
/**
 * @ClassName AbstractMacBookProduct
 * @Description 所有MacBook抽象产品类,可以理解成各种型号的MacBook笔记本
 * @Author MatthewHan
 * @Date 2019/8/1 18:02
 * @Version 1.0
 **/
@Setter(value = AccessLevel.PUBLIC)
@Getter(value = AccessLevel.PUBLIC)
@ToString
@AllArgsConstructor
@NoArgsConstructor
public abstract class AbstractMacBookProduct {
    private String sn;
    private String type;
    private String display;
    private String keyboard;
    private String weight;
    private Double price;

    /**
     * 打印结账语
     * @return
     */
    public abstract String printSlogan();
}
一类MacBook产品的抽象产品,差异化的产品配置。

抽象二兄弟的实现比较简单,其中抽象工厂接口定义的是 生产MacBook 的方法,就像是和各个工厂之间签了一份略有差别的合同,那么在具体生产的工厂中需要严格按照这份合同执行。抽象产品类则是体现 is-a 关系,更像是把这类产品的雏形给雕琢出来的模具,是具体产品的爹,具体工厂按照合同去生产合规的产品。

/**
 * @ClassName MacBook13Factory
 * @Description MacBook 13-inch的具体工厂
 * @Author MatthewHan
 * @Date 2019/8/2 09:12
 * @Version 1.0
 **/
public class MacBook13Factory implements MacBookFactory {
    @Override
    public AbstractMacBookProduct createMacBook() {
        MacBook13Product macBook13 = new MacBook13Product();
        macBook13.setSn(RandomUtil.getStr());
        macBook13.setType("MacBook Pro 13-inch");
        macBook13.setDisplay("13-inch");
        macBook13.setKeyboard("new keyboard");
        macBook13.setPrice(999D);
        macBook13.setWeight("88kg");
        /*
         * 游戏大礼包竟然是!
         * 《坦克大战乔碧萝》
         */
        macBook13.setGameGiftBag("《坦克大战乔碧萝》");
        return macBook13;
    }
}
/**
 * @ClassName MacBook13Product
 * @Description MacBook 13-inch具体产品类
 * @Author MatthewHan
 * @Date 2019/8/2 09:37
 * @Version 1.0
 **/
@Setter(value = AccessLevel.PUBLIC)
@Getter(value = AccessLevel.PUBLIC)
@ToString
public class MacBook13Product extends AbstractMacBookProduct {

    /**
     * 13-inch MacBook附赠游♂戏大礼包
     */
    private String gameGiftBag;

    @Override
    public String printSlogan() {
        return "This is your new MacBook 13-inch.";
    }
}
13-inch MacBook的具体工厂和他生产的具体产品13-inch MacBook 13-inch MacBook具体工厂也按照`合同`和`模具`对MacBook进行批量生产组装加工,`MacBook13Product`通过`extend`的方式,完全继承了模具的属性和行为,其中《坦克大战乔碧萝》这个游戏礼包是该产品的特有属性。
/**
 * @ClassName MacBook15Factory
 * @Description MacBook 15-inch具体工厂
 * @Author MatthewHan
 * @Date 2019/8/2 09:30
 * @Version 1.0
 **/
public class MacBook15Factory implements MacBookFactory {
    
    @Override
    public AbstractMacBookProduct createMacBook() {
        MacBook15Product macBook15 = new MacBook15Product();
        macBook15.setSn(RandomUtil.getStr());
        macBook15.setType("MacBook Pro 15-inch");
        macBook15.setDisplay("15-inch");
        macBook15.setKeyboard("new keyboard");
        macBook15.setPrice(1999D);
        macBook15.setWeight("88kg");
        /*
         * 蕴含着神秘力量的密码
         * 带你找回丢失的纯真时光
         */
        macBook15.setCode("magnet:?xt=urn:btih:36AAB086D9AF39A323082CBAD452D6BDC42147D1");
        return macBook15;
    }
}

/**
 * @ClassName MacBook15Product
 * @Description MacBook 15-inch具体产品类
 * @Author MatthewHan
 * @Date 2019/8/2 09:37
 * @Version 1.0
 **/
@Setter(value = AccessLevel.PUBLIC)
@Getter(value = AccessLevel.PUBLIC)
@ToString
public class MacBook15Product extends AbstractMacBookProduct {

    /**
     * 15-inch MacBook附赠的神秘代♂码
     */
    private String code;

    @Override
    public String printSlogan() {
        return "This is your new MacBook 15-inch.";
    }
}

15-inch MacBook的具体工厂和他生产的具体产品15-inch MacBook 15-inch MacBook具体工厂也按照`合同`和`模具`对MacBook进行批量生产组装加工,`MacBook13Product`通过`extend`的方式,完全继承了模具的属性和行为,其中神秘代码是该产品的特有属性。

来测试一下这些代工厂 996 生产的产品到底合不合规:

@Test
public void createMacBook() {
    /*
     * 抽象==================>具体
     */
    MacBookFactory macBook13Factory = new MacBook13Factory();
    /*
     * 13-inch MacBook实例化对象就由MacBook13Factory创建
     */
    AbstractMacBookProduct mac13WithMatthew = macBook13Factory.createMacBook();
     
    /*
     * 具体工厂生产的对象与具体产品类实例化的产品
     */
    MacBook13Product c = new MacBook13Product();
    assertEquals(c.getClass(), mac13WithMatthew.getClass());
    
    System.out.println(mac13WithMatthew.getClass());
    System.out.println(mac13WithMatthew.printSlogan());
    System.out.println(mac13WithMatthew);
}
@Test
public void createMacBook() {

    /*
     * 抽象==================>具体
     */
    MacBookFactory macBook15Factory = new MacBook15Factory();
    /*
     * 15-inch MacBook实例化对象就由MacBook15Factory创建
     */
    AbstractMacBookProduct mac15WithMatthew = macBook15Factory.createMacBook();

    /*
     * 具体工厂生产的对象与具体产品类实例化的产品
     */
    MacBook15Product c = new MacBook15Product();
    assertEquals(c.getClass(), mac15WithMatthew.getClass());
    
    System.out.println(mac15WithMatthew.getClass());
    System.out.println(mac15WithMatthew.printSlogan());
    System.out.println(mac15WithMatthew);
}

单元测试
单元测试

System.out.println(mac13WithMatthew) 打印的结果只有 gameGiftBag 是因为子类重写父类的 toString() 方法,如果把子类(MacBook13Product)的 @ToString 注解去掉的话,就是默认继承的父类(AbstractMacBookProduct)的 toString() 方法了。

避免滥用

事实上,在 SpringBoot 中已经用到了不少设计模式,在单例模式那章讲过的 Bean 就用到了单例模式和今天讲的工厂模式(很怀念第一次使用 Spring 框架手写第一个 Bean 的时候),模板方法(Template Method),JdbctempldateRedistemplate 等等。但是切记一定不要过于拘泥与死板,为了设计模式而设计模式,忽略本身业务场景和实际情况。模式本身是对编程思想的扩展,我们在编写代码的时候还是要专注于业务本身,设计模式的初衷就是解决问题采用最优解,为了追求更高效率而诞生,保护需要加班的你。最靠谱的还是实践中慢慢总结,踩过的坑自己去总结、优化。

  • 工厂模式
    6 引用
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...