工厂模式

概念

工厂模式 - 图1

简单工厂

代码实现

原始写法 0.0

接下来上代码,以某学院课程为例,目前开设有 Java, Python, 大数据,人工智能等课程。

定义课程标准接口 ICourse

  1. public interface ICourse {
  2. /**
  3. * 录制视频
  4. *
  5. * @return
  6. */
  7. void record();
  8. }

创建课程 JavaCourse 类

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

客户端调用代码

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

运行结果

  1. 录制 Java 课程

上面的代码中,父类 ICourse 指向子类 JavaCourse 的引用,应用层代码需要依赖 JavaCourse, 若业务扩展,继续增加 PythonCourse 课程甚至更多,客户端的依赖会越来越臃肿。虽然现在创建对象的过程并不复杂,但从代码设计的角度来讲不易于扩展。

简单工厂模式 1.0

下面用简单工厂模式进行优化,把这种依赖减弱,把创建细节隐藏。

新增课程 PythonCourse 类

  1. public class PythonCourse implements ICourse {
  2. public void record() {
  3. System.out.println("录制 Python 课程");
  4. }
  5. }

创建 CourseFactory 工厂类

  1. public class CourseFactory {
  2. public 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 courseFactory = new CourseFactory();
  4. ICourse course = courseFactory.create("python");
  5. course.record();
  6. }
  7. }

为了调用方便,CourseFactory 类的 create() 方法可以改为静态方法

  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. ICourse course = CourseFactory.create("python");
  4. course.record();
  5. }
  6. }

运行结果

  1. 录制 Python 课程

类图

工厂模式 - 图2

简单工厂模式 2.0

客户端调用简单了,如果继续扩展业务,比如增加前端课程,工厂类的 create() 方法的代码逻辑每次都要修改,不符合开闭原则。

可以采用反射技术继续优化

修改 CourseFactory 工厂类

  1. public class CourseFactory {
  2. public ICourse create(String className) {
  3. try {
  4. if (!(null == className || "".equals(className))) {
  5. return (ICourse) Class.forName(className).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. ICourse course = CourseFactory.create("com.yuzhe.design.pattern.factory.JavaCourse");
  4. course.record();
  5. }
  6. }

运行结果

  1. 录制 Java 课程

简单工厂模式 3.0

优化之后,丰富产品不再需要修改 CourseFactory 中的代码,但方法参数是字符串,可控性较差,而且还需要强制类型转换。

再次优化 CourseFactory 工厂类

  1. public class CourseFactory {
  2. public ICourse create(Class<? extends ICourse> clazz) {
  3. try {
  4. if (null != clazz) {
  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. ICourse course = CourseFactory.create(PythonCourse.class);
  4. course.record();
  5. }
  6. }

运行结果

  1. 录制 Python 课程

类图

工厂模式 - 图3

源码中的应用

JDK 中的 Calendar 类的 getInstance() 类,下面是具体的创建方法

  1. private static Calendar createCalendar(TimeZone zone,
  2. Locale aLocale)
  3. {
  4. CalendarProvider provider =
  5. LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
  6. .getCalendarProvider();
  7. if (provider != null) {
  8. try {
  9. return provider.getInstance(zone, aLocale);
  10. } catch (IllegalArgumentException iae) {
  11. // fall back to the default instantiation
  12. }
  13. }
  14. Calendar cal = null;
  15. if (aLocale.hasExtensions()) {
  16. String caltype = aLocale.getUnicodeLocaleType("ca");
  17. if (caltype != null) {
  18. switch (caltype) {
  19. case "buddhist":
  20. cal = new BuddhistCalendar(zone, aLocale);
  21. break;
  22. case "japanese":
  23. cal = new JapaneseImperialCalendar(zone, aLocale);
  24. break;
  25. case "gregory":
  26. cal = new GregorianCalendar(zone, aLocale);
  27. break;
  28. }
  29. }
  30. }
  31. if (cal == null) {
  32. // If no known calendar type is explicitly specified,
  33. // perform the traditional way to create a Calendar:
  34. // create a BuddhistCalendar for th_TH locale,
  35. // a JapaneseImperialCalendar for ja_JP_JP locale, or
  36. // a GregorianCalendar for any other locales.
  37. // NOTE: The language, country and variant strings are interned.
  38. if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
  39. cal = new BuddhistCalendar(zone, aLocale);
  40. } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
  41. && aLocale.getCountry() == "JP") {
  42. cal = new JapaneseImperialCalendar(zone, aLocale);
  43. } else {
  44. cal = new GregorianCalendar(zone, aLocale);
  45. }
  46. }
  47. return cal;
  48. }

slf4j 中的 LoggerFactory 有多个重载的 getLogger() 方法

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

工厂方法

代码实现

在简单工厂中,随着产品链的丰富,如果每个课程的创建逻辑有区别,工厂的职责会变得越来越重,不便维护。

根据单一职责原则,将职能拆分,专人干专事,Java 课程由 Java 工厂创建,Python 课程由 Python 工厂创建,对工厂本身也做一个抽象。

创建 ICourseFactory 接口

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

分别创建子工厂 JavaCourseFactory 类和 PythonCourseFactory 类

  1. public class JavaCourseFactory implements ICourseFactory {
  2. public ICourse create() {
  3. return new JavaCourse();
  4. }
  5. }
  1. public class PythonCourseFactory implements ICourseFactory {
  2. public ICourse create() {
  3. return new PythonCourse();
  4. }
  5. }

测试代码

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

运行结果

  1. 录制 Python 课程
  2. 录制 Java 课程

类图

工厂模式 - 图4

源码中的应用

slf4j 工厂方法模式的应用,类图如下

工厂模式 - 图5

工厂模式 - 图6

抽象工厂

代码实现

一个完整的课程包括笔记、视频、源码等内容,在产品等级中增加两个产品 IVideo 录播视频和 INote 课堂笔记

IVideo 接口

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

INote 接口

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

创建抽象工厂 CourseFactory 类

  1. public abstract class CourseFactory {
  2. public void init(){
  3. System.out.println("初始化基础数据");
  4. }
  5. protected abstract INote createNote();
  6. protected abstract IVideo createVideo();
  7. }

创建 Java 产品族,Java 视频 JavaVideo 类

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

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

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

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

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

然后创建 Python 产品族,Python 视频 PythonVideo 类

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

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

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

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

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

客户端调用

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

运行结果输出

  1. 初始化基础数据
  2. 编写 Java 笔记
  3. 初始化基础数据
  4. 录制 Java 视频

类图

工厂模式 - 图7

上述代码完整的描述了两个产品族:Java 课程和 Python 课程,也描述了两个产品等级:视频和笔记。抽象工厂完美清晰的描述了这样一层复杂的关系,从两个维度将一个复杂产品进行分解。

但如果继续扩展产品等级结构,如将源码 SourceCode 也加入课程中,代码从抽象工厂到具体工厂都要全部调整,显然不符合开闭原则。