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

禁止私下搞对象,对象统一由工厂发放!

Posted by MatthewHan on 2019-08-01

禁止私下搞对象,对象统一由工厂发放!

本系列已经开源至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)角色:这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。

本文实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @ClassName MacBookFactory
* @Description MacBook的抽象工厂类
* @Author MatthewHan
* @Date 2019/8/1 18:01
* @Version 1.0
**/
public interface MacBookFactory {

/**
* MacBook抽象工厂
* @return
*/
AbstractMacBookProduct createMacBook();
}
MacBook的抽象工厂类,用于描述所有具体型号生产的MacBook工厂的抽象基类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @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关系,更像是把这类产品的雏形给雕琢出来的模具,是具体产品的爹,具体工厂按照合同去生产合规的产品。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @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`的方式,完全继承了模具的属性和行为,其中《坦克大战乔碧萝》这个游戏礼包是该产品的特有属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @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生产的产品到底合不合规:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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等等。但是切记一定不要过于拘泥与死板,为了设计模式而设计模式,忽略本身业务场景和实际情况。模式本身是对编程思想的扩展,我们在编写代码的时候还是要专注于业务本身,设计模式的初衷就是解决问题采用最优解,为了追求更高效率而诞生,保护需要加班的你。最靠谱的还是实践中慢慢总结,踩过的坑自己去总结、优化。