【设计模式 - 1】之工厂模式(Factory)

1      模式简介

1.1    工厂模式作用

l  工厂模式解决的是“使用new关键字获取对象造成松耦合”的问题。

工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。

 

1.2    工厂模式分类

1.2.1  分类

工厂模式可以分为三类:

简单工厂模式(SimpleFactory

工厂方法模式(FactoryMethod

抽象工厂方法(AbstractFactory

 

1.2.2  分类简介

注:这里将简单工厂模式看作工厂方法模式的一个特例

1.2.2.1       工厂方法模式

一个抽象产品类,可以派生出多个具体产品类;

一个抽象工厂类,可以派生出多个具体工厂类;

每个具体工厂类只能创建一个具体产品类的实例。

 

1.2.2.2       抽象工厂模式

多个抽象产品类,每个抽象产品类可以派生出多个具体产品类;

一个抽象工厂类,可以派生出多个具体工厂类;

每个具体工厂类可以创建多个具体产品类的实例。

 

1.2.3  区别

工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个;

工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。

 

2      简单工厂模式

2.1    简介


简单工厂模式的工作流程如上图所示:汽车工厂中有创建汽车的方法,用户只需要将汽车的型号作为参数传到工厂中(告诉工厂自己想要造哪种型号的汽车),工厂会根据参数创建不同的汽车对象后返回给用户。

 

可见,在工厂模式中,用户不需要知道汽车具体是如何制造的,他们只需要告诉工厂自己想要哪个型号的汽车,其他的工作交给工厂去做即可。

 

简单工厂模式主要包括三部分:

工厂角色:相当于上图中的FactoryBMW,具有一定的判断逻辑;

抽象产品角色:相当于上图中的BMW,一般是具体商品继承的父类后实现的接口;

具体产品角色:相当于上图中的BMW532BMW320,是抽象产品类的子类或实现类。

 

2.2    分析

设计模式的最终目标:让程序“对扩展开放,对修改关闭(开闭原则)”。

 

当有新的车型加入工厂生产线后,不仅需要新加一个新的车型类,还需要在工厂中改变车型的判断逻辑,这是不符合开闭原则的。

 

要解决这个问题,需要工厂方法模式。

 

2.3    代码

程序目录架构:

 

抽象汽车产品类BMW.java类中的代码:

public abstract class BMW {
      public abstract void introduce();
}

具体汽车产品类BMW523.java和BMW320.java中的代码(以BMW523.java为例):

public class BMW523 extends BMW {
      public BMW523() {
      }
 
      @Override
      public void introduce() {
           System.out.println("这是一辆BMW523型号车");
      }
}
汽车工厂类FactoryBMW.java中的代码:

public class FactoryBMW {
      public static final int TYPE_BMW523 = 1;
      public static final int TYPE_BMW320 = 2;
 
      public static BMW createBMW(int type) {
           switch (type) {
           case TYPE_BMW523:
                 return new BMW523();
           case TYPE_BMW320:
                 return new BMW320();
           }
           return null;
      }
}
测试类Test.java中的代码:

public class Test {
      private static BMW bmw;
 
      public static void main(String[] args) {
           Scannerscanner = new Scanner(System.in);
           System.out.println("请选择要生产的汽车的型号:");
           System.out.println("1、BMW 523");
           System.out.println("2、BMW 320");
           int type = scanner.nextInt();
 
           bmw = FactoryBMW.createBMW(type);
           if (bmw != null) {
                 bmw.introduce();
           }else {
                 System.out.println("没有这个车型!");
           }
 
           scanner.close();
      }
}
运行结果:


3      工厂方法模式

3.1    简介

工厂方法模式的工作流程如上图所示:将简单工厂模式中的工厂类FactoryBMW定义成接口,每种车型都定义一个新的工厂类,用来专门生产BMW类的某个车型子类。这样,工厂可以扩展,就不需要修改原来的代码了。

 

工厂方法模式主要包括四部分:

l  抽象工厂角色:相当于上图中的FactoryBMW,是所有具体工厂必须实现或继承的接口或父类;

l  具体工厂角色:相当于上图中的FactoryBMW320和FactoryBMW523,它们含有具体的与业务逻辑有关的代码,用来创建具体的产品对象;

l  抽象产品角色:相当于上图中的BMW,是具体产品的接口或父类;

l  具体产品角色:相当于上图中的BMW523和BMW320,是具体的产品类。

3.2    分析

相比于简单工厂模式用一个工厂来创建所有车型,工厂方法模式将工厂生产汽车的方法下放到其子类中,分担了工厂类承担的压力。

 

当有新的车型加入工厂生产线时,只需要创建具体的工厂类和车型类即可,不需要修改现有的代码,让程序变得更加灵活,可见,工厂方法模式是符合开闭原则的。

 

但是工厂方法模式也有一定的弊端:这种模式使得类的数量成倍增长,当有很多车型时,就会有很多工厂类来生产这些车型,这不是我们所希望的。这个问题的一个解决方案是将工厂方法模式与简单工厂模式结合使用,创建方法比较相似的车型合并到一个简单工厂中创建,创建方法差别较大的车型使用工厂方法模式分到不同的工厂中创建。

 

工厂方法模式已经比较完美的对对象进行了封装,但也不是意味着我们要对程序中的所有对象都采用工厂方法模式。在以下情况下可以考虑使用工厂方法模式:

l  当客户不需要知道要使用的对象的创建过程时;

l  当客户要使用的对象存在变动的可能,或者根本就不知道要使用哪个对象时。

 

3.3    代码

程序目录框架:

 

抽象的工厂类FactoryBMW.java中的代码:

public abstract class FactoryBMW {
      public abstract BMW createBMW();
}
具体的工厂类FactoryBMW320.java和FactoryBMW523.java中的代码(这里以FactoryBMW523.java为例):

public class FactoryBMW523 extendsFactoryBMW {
 
      @Override
      public BMW createBMW() {
           return new BMW523();
      }
}
测试类Test.java中的代码:

public class Test {
      private static BMW bmw;
      private static FactoryBMW factory;
 
      public static void main(String[] args) {
           Scannerscanner = new Scanner(System.in);
           System.out.println("请选择要生产的汽车的型号:");
           System.out.println("1、BMW 523");
           System.out.println("2、BMW 320");
           int type = scanner.nextInt();
 
           switch (type) {
           case 1:
                 factory = new FactoryBMW523();
                 break;
           case 2:
                 factory = new FactoryBMW320();
                 break;
           default:
                 factory = null;
                 break;
           }
          
           if (factory != null) {
                 bmw = factory.createBMW();
                 bmw.introduce();
           }else {
                 System.out.println("没有这个车型!");
           }
 
           scanner.close();
      }
}
运行结果如下图:

3.4    扩展

从上面的代码我们可以看到:工厂方法模式其实并没有做到避免代码的修改,而是将原来需要在工厂中进行的逻辑判断拿到了测试类中进行,这是因为我们仍然无法判断具体需要调用哪个工厂。面对这种情况,我们可以使用反射机制来在一定程度上进行解决。具体代码如下:

public class Test {
      private static BMW bmw;
 
      private static final String[] classes = {
                 "com.itgungnir.designpattern.factory.factorymethod.FactoryBMW523",
                 "com.itgungnir.designpattern.factory.factorymethod.FactoryBMW320" };
 
      public static void main(String[] args) {
           Scannerscanner = new Scanner(System.in);
           System.out.println("请选择要生产的汽车的型号:");
           System.out.println("1、BMW 523");
           System.out.println("2、BMW 320");
           int type = scanner.nextInt();
 
           try {
                 Class<?>factory = Class.forName(classes[type - 1]);
                 MethodcreateBMW = factory.getMethod("createBMW");
                 bmw = (BMW) createBMW.invoke(factory.newInstance());
           }catch (ClassNotFoundException e) {
                 e.printStackTrace();
           }catch (NoSuchMethodException e) {
                 e.printStackTrace();
           }catch (SecurityException e) {
                 e.printStackTrace();
           }catch (IllegalAccessException e) {
                 e.printStackTrace();
           }catch (IllegalArgumentException e) {
                 e.printStackTrace();
           }catch (InvocationTargetException e) {
                 e.printStackTrace();
           }catch (InstantiationException e) {
                 e.printStackTrace();
           }
 
           if (bmw != null) {
                 bmw.introduce();
           }else {
                 System.out.println("没有这个车型!");
           }
 
           scanner.close();
      }
}
这样,当有新的车型和工厂加入到生产线之后,除了创建具体工厂类和具体车型类,只需要在字符串数组classes中加入新创建的具体工厂类的包地址即可。这样可以将代码的改动降到最小。

 

注:关于反射机制的介绍可以参考:【JAVA - 基础】之反射的原理与应用

4      抽象工厂模式

4.1    简介

抽象工厂模式的工作流程和工厂方法模式的工作流程基本相似,唯一的不同是一个工厂生产的是一个产品的不同组件。下图中,一个具体工厂除了可以生产汽车之外,还可以生产与该型号汽车对应的空调。(本DEMO的前提是每个车型的汽车都有一款对应的空调,一个车型的汽车不能搭载另一个车型的汽车的空调)

 

抽象工厂模式的组成部分和工厂方法模式的组成部分相同。

4.2    分析

从上图中可以看到,抽象工厂模式的适用场景是系统中存在多个系列的产品(如汽车和空调)。实际上,抽象工厂模式和工厂方法模式的区别就在于需要创建的对象的复杂程度。用这个例子来说,就是当我们只需要生产汽车的时候,就可以选择工厂方法模式;当我们既需要生产汽车又需要生产空调时,就可以选择抽象工厂模式。

 

用一句话概括抽象工厂方法:给客户端提供一个接口,可以创建多个产品族中的产品对象。

4.3    代码

程序目录框架:

 

空调抽象类AC.java中的代码:

public abstract class AC {
      public abstract void introduce();
}
具体的空调类AC523.javaAC320.java中的代码(这里以AC523.java为例):

public class AC523 extends AC {
 
      @Override
      public void introduce() {
           System.out.println("BMW523型号的汽车成功搭载了AC523型号的空调");
      }
}
抽象的工厂接口Factory.java中的代码:

public interface Factory {
      BMWcreateBMW();
 
      ACcreateAC();
}
具体的工厂类Factory523.javaFactory320.java中的代码(这里以Factory523.java为例):

public class Factory523 implements Factory {
 
      @Override
      public BMW createBMW() {
           return new BMW523();
      }
 
      @Override
      public AC createAC() {
           return new AC523();
      }
}
测试类Test.java中的代码:

public class Test {
      private static BMW bmw;
      private static AC ac;
 
      private static final String[] classes = {
                 "com.itgungnir.designpattern.factory.abstractfactory.Factory523",
                 "com.itgungnir.designpattern.factory.abstractfactory.Factory320" };
 
      public static void main(String[] args) {
           Scannerscanner = new Scanner(System.in);
           System.out.println("请选择要生产的产品的型号:");
           System.out.println("1、523型号");
           System.out.println("2、320型号");
           int type = scanner.nextInt();
 
           try {
                 Class<?>factory = Class.forName(classes[type - 1]);
                 MethodcreateBMW = factory.getMethod("createBMW");
                 MethodcreateAC = factory.getMethod("createAC");
                 bmw = (BMW) createBMW.invoke(factory.newInstance());
                 ac = (AC) createAC.invoke(factory.newInstance());
           }catch (ClassNotFoundException e) {
                 e.printStackTrace();
           }catch (NoSuchMethodException e) {
                 e.printStackTrace();
           }catch (SecurityException e) {
                 e.printStackTrace();
           }catch (IllegalAccessException e) {
                 e.printStackTrace();
           }catch (IllegalArgumentException e) {
                 e.printStackTrace();
           }catch (InvocationTargetException e) {
                 e.printStackTrace();
           }catch (InstantiationException e) {
                 e.printStackTrace();
           }
 
           if (bmw != null && ac!=null) {
                 bmw.introduce();
                 ac.introduce();
           }else {
                 System.out.println("没有这个型号的产品!");
           }
 
           scanner.close();
      }
}
运行结果如下图所示:

5      后记

这个帖子中的所有例子都很简单,都是直接创建的,这样还不能把工厂模式淋漓尽致的体现出来。设想,如果一个产品的创建需要很多步骤,中间会产生很多很多的对象,而这些对象最终都没有返回(即这些对象对我们最终想要获得的对象只是辅助作用,我们不需要获取这些对象)。在这种情况下,我们如果每获取一个对象都调用这一套方法的话无疑是非常耗时耗力的。

 

因此,工厂模式不仅对“new”进行了封装,也在一定程度上对对象的创建过程进行了封装。使用工厂模式,可以得到更松耦合、更有弹性的设计。

 

最后总结一下工厂模式不同类别的定义:

l  工厂方法模式:定义一个创建对象的接口或抽象类,由实现类或子类决定要实例化的类是哪一个。工厂方法模式让类把实例化的代码推迟到子类中进行;

l  抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

 

最后贴出工厂模式的GitHub地址:【GitHub - Factory】


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章