菜鸟成长系列 - 概述
菜鸟成长系列 - 面向对象的四大基础特性
菜鸟成长系列 - 多态、接口和抽象类
菜鸟成长系列 - 面向对象的 6 种设计原则
菜鸟成长系列 - 单例模式

  1. 上一篇我们已经对创建型模式中的单例模式进行了学习,今天来学习另外一个比较重要并且经常使用的模式-工厂模式;工厂模式专门负责将大量有共同接口的类实例化。其可以动态的决定将哪一个类实例化,不必事先知道每次要实例化哪一个类。

工厂模式具有以下三种形态:

  • 简单工厂模式:又称静态工厂模式
  • 工厂方法模式:又称多态性工厂模式或者虚拟构造子模式
  • 抽象工厂模式:又称工具箱模式

本篇文章将对这三种形态的工厂模式进行一些基本的学习,并通过例子来直观的感受下不同形态的具体实现方式。最后再分析下 JAVA 以及 Spring 中是如何使用的。

1、简单工厂模式

从上图可以看出,简单工厂模式涉及到工厂角色、抽象产品角色以及具体产品角色等三个角色。各角色职能如下:
  • 工厂类:担任这个角色的是工厂方法模式的核心,含有与应用紧密相关的具体业务逻辑。工厂类在客户端的直接调用下创建产品对象,它往往由一个具体的 java 类实现
  • 抽象产品:担任这个角色的类是工厂方法模式所创建的对象的父类,或它们共同拥有的接口。抽象产品角色可以用一个 java 接口或者抽象类来实现
  • 具体产品:工厂方法模式所创建的任何对象都是这个角色的实例,具体产品角色由一个 java 类实现
来看例子,考虑到今天有小伙伴来我这做客,本 demo 将以做菜来实现一波。首先工厂就是厨房,抽象类就是笼统的菜,实现类就是具体哪个菜。
  • 抽象产品
  1. package com.glmapper.design.factory;
  2. /**
  3. * 抽象类角色:food接口,约束类型
  4. * @author glmapper
  5. * @date 2017年12月24日上午10:38:36
  6. *
  7. */
  8. public interface IFood {
  9. /**
  10. * 提供一个展示食物细节的方法
  11. * @param foodName 食物名称
  12. */
  13. public void showFood();
  14. }
  • 具体产品 - 鱼
  1. package com.glmapper.design.factory;
  2. /**
  3. * 具体产品-食物鱼
  4. * @author glmapper
  5. * @date 2017年12月24日上午10:51:29
  6. *
  7. */
  8. public class FishFood implements IFood
  9. {
  10. @Override
  11. public void showFood() {
  12. System.out.println("一盘鱼");
  13. }
  14. }
  • 具体产品 - 土豆丝
  1. package com.glmapper.design.factory;
  2. /**
  3. *
  4. * 具体食物:土豆丝
  5. * @author glmapper
  6. * @date 2017年12月24日上午10:47:17
  7. *
  8. */
  9. public class ShreddedPotatoesFood implements IFood{
  10. @Override
  11. public void showFood() {
  12. System.out.println("一盘土豆丝");
  13.  
  14. }
  15. }
  • 工厂角色 - 食物工厂
  1. package com.glmapper.design.factory;
  2. /**
  3. * 工厂角色-食物工厂
  4. *
  5. * @author glmapper
  6. * @date 2017年12月24日上午10:41:10
  7. *
  8. */
  9. public class SimpleFoodFactory {
  10. /**
  11. * 提供一个静态方法,用于获取食物
  12. * @param foodType 食物类型
  13. * @return 具体食物
  14. */
  15. public static IFood getFood(String foodType) {
  16. IFood food = null;
  17. if (foodType.equals("fish")) {
  18. food = new FishFood();
  19. }
  20. if (foodType.equals("potatoes")) {
  21. food = new ShreddedPotatoesFood();
  22. }
  23. return food;
  24. }
  25. }
  • 客户端
  1. package com.glmapper.design.factory;
  2. /**
  3. * 客户端
  4. * @author glmapper
  5. * @date 2017年12月24日上午10:45:17
  6. *
  7. */
  8. public class MainTest {
  9. public static void main(String[] args) {
  10. IFood fishfood = SimpleFoodFactory.getFood("fish");
  11. fishfood.showFood();
  12.  
  13. IFood potatoesfood = SimpleFoodFactory.getFood("potatoes");
  14. potatoesfood.showFood();
  15. }
  16. }
  • 结果
  1. 一盘鱼
  2. 一盘土豆丝

OK,菜做完了,可以吃了。。。

我们来讨论下简单工厂模式的优缺点:

  1. 优点:模式的核心是工厂类,这个类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例。而客户端则可以免除直接创建产品对象的责任,而仅仅负责消费产品即可。用一句话来说就是:简单工厂模式这个做法实现了对责任的分割。
  1. 缺点:集中了所有产品的创建逻辑,形成了一个无所不能的全职类,但是之前我们在讨论设计原则的时候说过,我们要尽量避免这种情况的发生,这种就很明显破坏了单一职责这条原则,另外也不满足开闭原则的约束。当我们需要进行品类扩展时,我们需要不断的去修改我们的工厂的业务逻辑,一方面是工厂类会急速的膨胀,另一方面因为囊括了不同的产品对于我们后期的维护造成一定的影响。

2、工厂方法模式

这个时候一个同事说他是南方人,另外一个同事说他是北方人,吃不惯今天的菜。

好吧,既然这样,那我就只能点外卖了。。。但是为了防止他们变卦自己的家乡,我需要做一个计划,下面就是计划图:

从上图中我们可以看出,工厂方法模式的角色包括以下几种:

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

因为我的同事都是来自不同地方的,他们的口味也都不一样,但是呢同事都是第一次来我家吃饭,所以为了招待周全,根据同事不同的口味叫不同口味的鱼。

  • 抽象工厂角色:获取食物
  1. package com.glmapper.design.factory;
  2. /**
  3. *
  4. * 角色1:抽象工厂 - 负责获取食物
  5. * @author glmapper
  6. * @date 2017年12月24日下午1:59:28
  7. */
  8. public interface MethodFoodFactory {
  9. //获取食物的方法
  10. public IFishFood getFood();
  11. }
  • 具体工厂 1:获取南方食物 - 鱼
  1. package com.glmapper.design.factory;
  2. /**
  3. * 南方口味外卖 - 鱼
  4. * @author glmapper
  5. * @date 2017年12月24日下午2:03:36
  6. */
  7. public class SouthFishFactory implements MethodFoodFactory{
  8. @Override
  9. public IFishFood getFood() {
  10. return new SouthFishFood();
  11. }
  12. }
  • 具体工厂 2:获取北方食物 - 鱼
  1. package com.glmapper.design.factory;
  2. /**
  3. * 北方口味外卖 - 鱼
  4. * @author glmapper
  5. * @date 2017年12月24日下午2:03:36
  6. */
  7. public class NorthFishFactory implements MethodFoodFactory {
  8.  
  9. @Override public IFishFood getFood() {
  10. // TODO Auto-generated method stub
  11. return new NorthFishFood();
  12. }
  13. }
  • 具体产品 1:南方食物 - 鱼
  1. package com.glmapper.design.factory;
  2. /**
  3. * 南方口味-鱼
  4. * @author glmapper
  5. * @date 2017年12月24日下午2:16:17
  6. */
  7. public class SouthFishFood implements IFishFood{
  8. @Override
  9. public void showFood() {
  10. System.out.println("来自南方厨师做的鱼");
  11. }
  12. }
  • 具体产品 2:北方食物 - 鱼
  1. package com.glmapper.design.factory;
  2. /**
  3. * 北方口味 - 鱼
  4. * @author glmapper
  5. * @date 2017年12月24日下午2:12:55
  6. */
  7. public class NorthFishFood implements IFishFood {
  8. @Override
  9. public void showFood() {
  10. System.out.println("来自北方厨师做的鱼");
  11. }
  12. }
  • 客户端
  1. package com.glmapper.design.factory;
  2. /**
  3. * 客户端
  4. * @author glmapper
  5. * @date 2017年12月24日上午10:45:17
  6. */
  7. public class MainTest {
  8. public static void main(String[] args) {
  9. //点一个南方口味外卖
  10. MethodFoodFactory southFoodFactory = new SouthFishFactory();
  11. //点一个北方口味外卖
  12. MethodFoodFactory northFoodFactory = new NorthFishFactory();
  13. //拿到南方口味外卖鱼
  14. southFoodFactory.getFood().showFood();
  15. //拿到北方口味外卖鱼
  16. northFoodFactory.getFood().showFood();
  17. }
  18. }
  • 结果:
  1. 来自南方厨师做的鱼
  2. 来自北方厨师做的鱼

OK,这样我们就满足了不同区域同时关于鱼口味的需求了,以后升值加薪就指望他们了。。。

关于工厂方法模式的优缺点:

优点:

  1. 1 在工厂方法中,用户只需要知道所要产品的具体工厂,无须关系具体的创建过程,甚至不需要具体产品类的类名。
  1. 2 在系统增加新的产品时,我们只需要添加一个具体产品类和对应的实现工厂,无需对原工厂进行任何修改,很好地符合了"开闭原则"
缺点:
  1. 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,是的系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

3、抽象工厂模式

准备吃饭的时候突然又来了几位同事,而且他们有的喜欢吃酸菜鱼,有的喜欢吃红烧鱼,这就很头疼。于是,只能根据他们的需要继续点外卖。(这个就给出一个结构图,并且将每种角色都用具体的场景来说明了,具体的代码可以参考这个图例自己尝试一下。)

OK,终于可以吃饭了!

4、三种形态的工厂模式在 java 中的使用

4.1、简单工厂模式在 java 中的使用

java.text.DateFormat (一个抽象类)这个类相信很多小伙伴都用到过,在 java API 中,这个类算是一个简单工厂模式的典型应用了(此处还是与上篇一样,不考虑期源码细节,也不介绍基本用法)。来看它的几个方法:

  • public final static DateFormat getDateInstance()
  • public final static DateFormat getDateInstance(int style)
  • public final static DateFormat getDateInstance(int style, Locale aLocale)

作为一个抽象类,却提供了很多的静态工厂方法,就像上面列举的那三个一样。有小伙伴可能会疑惑,为啥子一个抽象类阔以有自己的实例,并通过几个方法提供自己的实例。我们知道,抽象类是不可以有自己的实例对象的,但是需要注意的是,DateFormat 的工厂方法是静态的,并不是普通的方法,也就是说,不需要通过创建实例对象的方式去调用。

  1. public final static DateFormat getDateInstance()
  2. {
  3. return get(0, DEFAULT, 2, Locale.getDefault(Locale.Category.FORMAT));
  4. }

getDateInstance 方法并没有通过调用 DateFormat 的构造方法来创建对象。

4.2、工厂方法模式在 java 中的应用

java.net.URL 类,类图如下,

URL 对象通过一个工厂方法 openConnection() 返回一个 URLConnection 类型的对象。URLConnection 是一个抽象类,因此所返还的不可能是这个抽象类的实例,而必然是其具体子类的实例。

4.3、抽象工厂模式在 java 中的应用

根据 java 与模式一书的介绍,在 java 中使用抽象工厂模式的是 JAVA awt 的 peer 架构,通过抽象工厂模式来构建分属于不同操作系统的 peer 构件。这个我也没用过,了解即可。

关于 Spring 中工厂模式的使用会在后续 Spring 源码分析系列中给大家详细分析,这里就不重复了。

今天的学习就到此结束了,祝大家周末愉快。话说今天平安夜,大家都是在家写代码吗?

  1. 如果您对系列文章有任何意见,可以给我留言,感谢大家。

下面是一个接苹果的姿势,呼呼呼.....