工厂模式有三种实现方式:

  • 简单工厂模式

  • 工厂方法模式

  • 抽象工厂模式

通常由应用程序直接使用 new 创建新的对象,为了将对象的创建和使用相分离,采用工厂模式,即应用程序将对象的创建及初始化职责交给工厂对象。

简单工厂模式

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

简单工厂适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象不需要关心。Spring 中的 BeanFactory 就是简单工厂模式的体现,根据传入一个唯一的标识来获得 Bean 对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。

以课程为例,定义一个课程标准 ICourse 接口:

  1. public interface ICourse {
  2. //录制课程
  3. void record();
  4. }

创建一个 Java 课程的实现 JavaCourse 类:

  1. public class JavaCourse implements ICourse {
  2. @Override
  3. public void record() {
  4. System.out.println("录制 Java 课程");
  5. }
  6. }

我们会这样写客户端调用代码:

  1. public static void main(String[] args) {
  2. ICourse course = new JavaCourse();
  3. course.record();
  4. }

看上面的代码,父类 ICourse 指向子类 JavaCourse 的引用,应用层代码需要依赖JavaCourse,如果业务扩展,我继续增加 PythonCourse 甚至更多,那么我们客户端的依赖会变得越来越臃肿。因此,我们要想办法把这种依赖减弱,把创建细节隐藏。虽然目前的代码中,我们创建对象的过程并不复杂,但从代码设计角度来讲不易于扩展。现在,我们用简单工厂模式对代码进行优化。先增加课程 PythonCourse 类:

  1. public class PythonCourse implements ICourse {
  2. @Override
  3. public void record() {
  4. System.out.println("录制 Python 视频");
  5. }
  6. }

创建工厂类 CourseFactory:

  1. public class CourseFactory {
  2. public static ICourse create(String name) {
  3. if ("java".equals(name)) {
  4. return new JavaCourse();
  5. } else if ("python".equals(name)) {
  6. return new PythonCourse();
  7. } else {
  8. return null;
  9. }
  10. }
  11. }

修改客户端调用代码如下:

  1. public class SimpleFactoryTest {
  2. public static void main(String[] args) {
  3. CourseFactory factory = new CourseFactory();
  4. factory.create("java");
  5. }
  6. }

当然,为了调用方便,可将工厂中的 create() 方法改为静态方法,下面来看一下类图,如下图所示。

image.png

客户端调用变简单了,但如果我们业务继续扩展,要增加前端课程,那么工厂中的 create() 方法就要每次都根据产品的增加修改代码逻辑,不符合开闭原则。因此,我们还可以对简单工厂模式继续优化,采用反射技术:

  1. public class CourseFactory {
  2. public static ICourse create(String className) {
  3. if (!(null == className || "".equals(className))) {
  4. try {
  5. return (ICourse) Class.forName(className).newInstance();
  6. } catch (Exception e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. return null;
  11. }
  12. }

修改客户端调用代码:

  1. public class SimpleFactoryTest {
  2. public static void main(String[] args) {
  3. CourseFactory factory = new CourseFactory();
  4. ICourse course = factory.create("com.yjw.demo.pattern.factory2.simple.JavaCourse");
  5. course.record();
  6. }
  7. }

优化之后,产品不断丰富的过程中不需要修改 CourseFactory 中的代码。但还有个问题是,方法参数是字符串,可控性有待提升,而且还需要强制转型。再修改一下代码:

  1. public class CourseFactory {
  2. public static ICourse create(Class<? extends ICourse> clazz) {
  3. try {
  4. if (clazz != null) {
  5. return clazz.newInstance();
  6. }
  7. } catch (Exception e) {
  8. e.printStackTrace();
  9. }
  10. return null;
  11. }
  12. }

优化客户端代码:

  1. public class SimpleFactoryTest {
  2. public static void main(String[] args) {
  3. CourseFactory factory = new CourseFactory();
  4. ICourse course = factory.create(JavaCourse.class);
  5. course.record();
  6. }
  7. }

在看一下类图,如下图所示。

image.png

简单工厂模式也有它的缺点:工厂类的职责相对过重,不易于扩展过于复杂的产品结构。

工厂方法模式

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

工厂方法模式主要解决产品扩展的问题,在简单工厂中,随着产品链的丰富,如果每个课程的创建逻辑有区别,工厂的职责会变得越来越多,有点像万能工厂,并不便于维护。根据单一职责原则我们将职能继续拆分,专人干专事。Java 课程由 Java 工厂创建,Python 课程由 Python 工厂创建,对工厂本身也做一个抽象。来看代码,先创建ICourseFactory 接口:

  1. public interface ICourseFactory {
  2. ICourse crete();
  3. }

在分别创建子工厂,JavaCourseFactory 类的代码如下:

  1. public class JavaCourseFactory implements ICourseFactory {
  2. @Override
  3. public ICourse crete() {
  4. return new JavaCourse();
  5. }
  6. }

PythonCourseFactory 类的代码如下:

  1. public class PythonCourseFactory implements ICourseFactory {
  2. @Override
  3. public ICourse crete() {
  4. return new PythonCourse();
  5. }
  6. }

测试代码如下:

  1. public static void main(String[] args) {
  2. ICourseFactory factory = new PythonCourseFactory();
  3. ICourse course = factory.crete();
  4. course.record();
  5. factory = new JavaCourseFactory();
  6. course = factory.crete();
  7. course.record();
  8. }

现在再来看一下类图,如下图所示。

image.png

工厂方法适用于以下场景:

  • 创建对象需要大量重复的代码。
  • 客户端(应用层)不依赖于产品类实例如何被创建、如何被实现等细节。
  • 一个类通过其子类来指定创建哪个对象。

工厂方法的缺点:

  • 类的个数容易过多,增加复杂度。
  • 增加了系统的抽象性和理解难度。

抽象工厂模式

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

讲解抽象工厂模式之前,我们要了解两个概念:产品等级结构产品族。看下面这张图。

image.png

上图中有正方形、圆形和菱形三种图形,相同颜色深浅的代表同一个产品族,相同形状的代表同一个产品等级结构。同样可以做个类比,比如,美的电器生产多种家电电器,上图中颜色最深的正方形代表美的洗衣机、颜色最深的圆形代表美的空调、颜色最深的菱形代表美的热水器,颜色最深的一排都属于美的品牌,都属于美的的电器这个产品族。在看最右侧的菱形,颜色最深的我们指定它代表美的热水器,第二排颜色稍微浅一点的菱形可代表海信的热水器。同理,同一产品结构下还有格力热水器、格力空调、格力洗衣机。

在看下面的这张图,最左侧的小房子我们就认为是具体的工厂,有美的工厂,有海信工厂,有格力工厂,每个品牌的工厂都生产洗衣机、热水器和空调。

image.png

通过两张图的对比,相信大家对抽象工厂模式有了非常形象的理解。接下来我们来看一个具体的业务场景并用代码来实现。还是以课程为例,每个课程不仅要提供课程的录播视频,还要提供老师的课堂笔记。相当于现在的业务变更为同一个课程不单纯包含一个课程信息,同时要包含录播视频、课堂笔记,甚至还要提供源码才能构成一个完整的课程。在产品等级中增加两个产品:IVideo 录播视频和 INote 课堂笔记。

IVideo 接口如下:

  1. public interface IVideo {
  2. void record();
  3. }

INote 接口如下:

  1. public interface INote {
  2. void edit();
  3. }

然后创建一个抽象工厂类 CourseFactory:

  1. public interface CourseFactory {
  2. INote createNote();
  3. IVideo createVideo();
  4. }

接下来,创建 Java 产品族的 Java 视频类 JavaVideo:

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

扩展产品等级 Java 课堂笔记类 JavaNote:

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

创建 Java 产品族的具体工厂 JavaCourseFactory:

  1. public class JavaCourseFactory implements CourseFactory {
  2. @Override
  3. public INote createNote() {
  4. return new JavaNote();
  5. }
  6. @Override
  7. public IVideo createVideo() {
  8. return new JavaVideo();
  9. }
  10. }

然后创建 Python 产品的 Python 视频类 PythonVideo:

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

扩展产品等级 Python 课堂笔记类 PythonNote:

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

创建 Python 产品族的具体工厂 PythonCourseFactory:

  1. public class PythonCourseFactory implements CourseFactory {
  2. @Override
  3. public INote createNote() {
  4. return new PythonNote();
  5. }
  6. @Override
  7. public IVideo createVideo() {
  8. return new PythonVideo();
  9. }
  10. }

来看客户端调用代码:

  1. public static void main(String[] args) {
  2. JavaCourseFactory javaCourseFactory = new JavaCourseFactory();
  3. javaCourseFactory.createNote().edit();
  4. javaCourseFactory.createVideo().record();
  5. PythonCourseFactory pythonCourseFactory = new PythonCourseFactory();
  6. pythonCourseFactory.createNote().edit();
  7. pythonCourseFactory.createVideo().record();
  8. }

上面的代码完整地描述了两个产品族:Java 课程和 Python 课程,也描述了两个产品等级视频和笔记。抽象工厂模式非常完美清晰地描述了这样一层复杂的关系。但是,不知道大家有没有发现,如果我们再继续扩展产品等级,将源码 Source 也加入到课程中,那么我们的代码从抽象工厂到具体工厂要全部调整,很显然不符合开闭原则。由此可知抽象工厂也是有缺点的:

  • 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。
  • 增加了系统的抽象性和理解难度。

摘录:《Spring 5 核心原理与30个类手写实战》来自文艺界的Tom老师的书籍。

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/rx96bb 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。