架构师内功心法,经典框架都在用的工厂模式详解

一、经典框架都在用设计模式解决问题

Spring就是一个把设计模式用的淋漓尽致的经典框架,其实从类的名称就能够看出来,我们来一一列举一下:

特别需要说明的是,设计模式从来都不是单个设计模式独立使用的。 在通常情况下,经常是多个设计模式混合使用,你中有我,我中有你。所有的设计模式讲解都会围绕Spring的IOC、AOP、JDBC、MVC来进行展开。设计模式根据设计类型进行分类如下:

二、工厂模式详解

2.1 工厂模式的由来

在我们的现实生活当中,原始社会自给自足(没有工厂)、农耕社会的小作坊(简单工厂,民间酒坊)、工业革命流水线(工厂方法,自产自销)、现代产业链工厂(抽象工厂,富士康)




从现实生活联想到我们项目中的代码同样也是由简而繁一步一步迭代而来的,但是对于调用者确是越来越简单化。

2.2 简单工厂模式(Simple Factory Pattern)

简单工厂模式是指由一个工厂对象决定创建出哪一种产品的实例,但它不属于GOF,23设计模式。

参考资料维基百科地址:https://en.wikipedia.org/wiki/Design_Patterns#Patterns_by_Type

简单工厂模式适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关系。

接下来我们来举例,以高中学校课程为例。语文、数学、英语等多门学科。我们可以定义一个课程标准ICourse接口:

1
2
3
4
5
6
7
public interface ICourse {

/**
* 学习课程
*/
public void study();
}

创建一个语文课的实现ChineseCourse类:

1
2
3
4
5
6
7
8
9
10
11
public class ChineseCourse implements ICourse {
@Override
public void study() {
System.out.println("学习语文课");
}

public static void main(String[] args) {
ICourse course = new ChineseCourse();
course.study();
}
}

看上面的main方法中,应用层的代码需要依赖ChineseCourse,如果业务扩展,会继续增加MathCourse甚至更多,这样的话客户端的依赖会越来越臃肿的。所以我们需要对创建代码的细节进行隐藏,我们使用简单工厂模式对代码进行优化。先添加MathCourse类:

1
2
3
4
5
6
public class MathCourse implements ICourse {
@Override
public void study() {
System.out.println("学习数学课");
}
}

创建CourseFactory工厂类:

1
2
3
4
5
6
7
8
9
10
11
public class CourseFactory {
public ICourse create(String name) {
if("chinese".equals(name)) {
return new ChineseCourse();
}else if("math".equals(name)) {
return new MathCourse();
}else {
return null;
}
}
}

mian方法调用:

1
2
3
4
 public static void main(String[] args) {
CourseFactory courseFactory = new CourseFactory();
courseFactory.create("chinese");
}

客户端调用是简单了,但是我们的业务继续扩展,需要增加英文课,那么工厂中的create()方法要根据增加的业务每次都修改代码逻辑,不符合开闭原则。因此,我们还需要对简单工厂进行优化,利用反射技术:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CourseFactory {

public ICourse create(String className) {
try {
if(!(null == className || "".equals(className))) {
return (ICourse) Class.forName(className).newInstance();
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}

public static void main(String[] args) {
CourseFactory courseFactory = new CourseFactory();
ICourse course = courseFactory.create("com.sfp.ChineseCourse");
course.study();
}
}

优化之后,课程不断增加不需要修改CourseFactory中的代码了。但是,方法参数是字符串,可控性有待提高,而且还需进行强制转换。再次修改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CourseFactory {

public ICourse create(Class<? extends ICourse> clazz) {
try {
if(null != clazz) {
return clazz.newInstance();
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}

public static void main(String[] args) {
CourseFactory courseFactory = new CourseFactory();
ICourse course = courseFactory.create(ChineseCourse.class);
course.study();
}
}

简单工厂模式的例子无处不在,现在我们来看JDK当中的类使用简单工厂模式的例子,例如Calendar类,其中Calendar.getInstance()方法,我们查看源码具体的实现步骤:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
{
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}

Calendar cal = null;

if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
// If no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}

还有一个大家经常使用的logback,我们可以看到LoggerFactory中有多个重载的方法getLogger():

1
2
3
4
5
6
7
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName());
}

简单工厂的缺点:工厂的职责相对过重,不易于扩展过于复杂的代码结构。

2.3 工厂方法模式(Factory Method Pattern)

工厂方法模式是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。 在工厂方法模式中只关心所需产品对应的工厂,无心关注创建细节,而加入新的产品符合开闭原则。

工厂方式模式主要解决产品扩展的问题,根据单一职责原则将职能进行拆分,专人干专事。语文课由语文工厂创建,数据课由数学工厂创建,对工厂本身做一个抽象。示例代码如下:

创建ICourseFactory接口:

1
2
3
4
public interface ICourseFacotry {

ICourse create();
}

再分别创建子工厂,ChineseCourseFactory类:

1
2
3
4
5
6
public class ChineseCourseFactory implements ICourseFacotry {
@Override
public ICourse create() {
return new ChineseCourse();
}
}

MathCourseFactory类:

1
2
3
4
5
6
public class MathCourseFactory implements ICourseFacotry {
@Override
public ICourse create() {
return new MathCourse();
}
}

执行main方法:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
ICourseFacotry chineseCourseFactory = new ChineseCourseFactory();
ICourse chineseCourse = chineseCourseFactory.create();
chineseCourse.study();

ICourseFacotry mathCourseFactory = new MathCourseFactory();
ICourse mathCourse = mathCourseFactory.create();
mathCourse.study();
}

2.4 抽象工厂模式(Abstract Factory Pattern)

抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无需指定它们具体的类。 客户端(应用层)不依赖于产品实例如何被创建、实现等细节,强调的是一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码。需要提供一个产品库的类,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。

我们还是以课程的例子为例,现在是新冠状病毒疫情的时期,高中学生只能在家利用互联网进行在线直播上课,每个课程不仅要提供课程的录播视频,而且还要提供老师的课堂笔记。相当于现在的业务变更为同一个课程不单纯是课程信息,同时要包括录播视频、课堂笔记等才是一个完整的课程。在产品等级中增加两个产品接口Ivideo录播视频和INote课堂笔记。

Ivideo接口:

1
2
3
4
public interface IVideo {

void record();
}

INote接口:

1
2
3
4
public interface INote {

void edit();
}

创建抽象工厂类CourseFactory类:

1
2
3
4
5
6
public interface CourseFactory {

IVideo createVideo();

INote createNote();
}

创建语文课产品族,语文课视频的ChineseVideo类:

1
2
3
4
5
6
public class ChineseVideo implements IVideo {
@Override
public void record() {
System.out.println("录制语文课视频!");
}
}

创建语文课课堂笔记的ChineseNote类:

1
2
3
4
5
6
public class ChineseNote implements INote {
@Override
public void edit() {
System.out.println("编写语文课笔记!");
}
}

创建语文课产品族的具体工厂类ChineseCourseFactory:

1
2
3
4
5
6
7
8
9
10
11
public class ChineseCourseFactory implements CourseFactory {
@Override
public IVideo createVideo() {
return new ChineseVideo();
}

@Override
public INote createNote() {
return new ChineseNote();
}
}

然后再创建数学产品,Math视频MathVideo类:

1
2
3
4
5
6
public class MathVideo implements IVideo {
@Override
public void record() {
System.out.println("录制数学课视频!");
}
}

创建数学课堂笔记的MathNote类:

1
2
3
4
5
6
public class MathNote implements INote {
@Override
public void edit() {
System.out.println("编写数学课笔记!");
}
}

创建数学课产品族的具体工厂类MathCourseFactory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MathCourseFactory implements CourseFactory {
@Override
public IVideo createVideo() {
return new MathVideo();
}

@Override
public INote createNote() {
return new MathNote();
}

public static void main(String[] args) {
MathCourseFactory mathCourseFactory = new MathCourseFactory();
mathCourseFactory.createNote().edit();
mathCourseFactory.createVideo().record();
}
}

上面的案例代码完整的描述了两个产品族语文课程和数学课程,也描述了两个产品等级的视频和课堂笔记。抽象工厂非常完美清晰地描述了这一层复杂的关系。如果我们再升级扩展产品等级,将课堂作业也加入到课程中,我们的代码需要从抽象工厂,到具体的工厂都要进行调整,很显然不符合开闭原则。所以抽象工厂也是有缺点的:

1、规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。

2、增加了系统的抽象性和理解难度。

三、工厂模式总结