工厂模式

1、定义:

  • 定义一个用于创建对象的接口,让子类决定实例化哪个类,工厂类将一个类的实例化延迟到其子类。
  • 属于创建型模式

优点:

  • 可以使代码结构清晰,有效地封装变化。在编程中,产品类的实例化有时候是比较复杂和多变的,通过工厂模式,将产品的实例化封装起来,使得调用者根本无需关心产品的实例化过程,只需依赖工厂即可得到自己想要的产品。
  • 对调用者屏蔽具体的产品类。如果使用工厂模式,调用者只关心产品的接口就可以了,至于具体的实现,调用者根本无需关心。即使变更了具体的实现,对调用者来说没有任何影响。
  • 降低耦合度。产品类的实例化通常来说是很复杂的,它需要依赖很多的类,而这些类对于调用者来说根本无需知道,如果使用了工厂方法,我们需要做的仅仅是实例化好产品类,然后交给调用者使用。对调用者来说,产品所依赖的类都是透明的。

接下来我们介绍四种工厂模式,分为3大类,分别是1.静态工厂,2.工厂方法,3.抽象工厂

2、模式原理

2.1.1、简单工厂模式/静态工厂模式


简单工厂模式由三种角色组成:

  • 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。如例中的Car接口。
    public abstract class INoodles {
      /**
       * 描述每种面条啥样的
       */
      public abstract void getMessage();
    }
    
  • 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现,如例子中的LzNoodles类。
    public class LzNoodles extends INoodles {
      @Override
      public void getMessage() {
          System.out.println("兰州拉面");
      }
    }
    public class PaoNoodles extends INoodles {
      @Override
      public void getMessage() {
          System.out.println("泡面");
      }
    }
    public class ReGanNoodles extends INoodles {
      @Override
      public void getMessage() {
          System.out.println("热干面");
      }
    }
    
  • 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不同,产生具体的工厂产品。如例子中的Driver类。
    public class SimpleNoodlesFactory {
      public static final int TYPE_LZ = 1;//兰州拉面
      public static final int TYPE_PM = 2;//泡面
      public static final int TYPE_RG = 3;//热干面
    
      public static INoodles createNoodles(int type) {
          switch (type) {
              case TYPE_LZ:
                  return new LzNoodles();
              case TYPE_PM:
                  return new PaoNoodles();
              case TYPE_GK:
              default:
                  return new ReGanNoodles();
          }
      }
    }
    

测试:

/**
 * 简单工厂模式
 */
INoodles noodles = SimpleNoodlesFactory.createNoodles(SimpleNoodlesFactory.TYPE_RG);
noodles.getMessage();
 /*输出 热干面*/

特点

  1. 它是一个具体的类,非接口 抽象类。有一个重要的create()方法,利用if或者 switch创建产品并返回。
  2. create()方法通常是静态的,所以也称之为静态工厂

缺点

  1. 扩展性差,我想增加一种面条,除了新增一个面条产品类,还需要修改工厂类方法,违反了开闭原则。
  2. 不同的产品需要不同额外参数的时候 不支持。

2.1.2多方法静态工厂(常用)

简单工厂对于不同的产品需要不同额外参数的时候 不支持。

而且如果使用时传递的type、Class出错,将不能得到正确的对象,容错率不高。

而多方法的工厂模式为不同产品,提供不同的生产方法,使用时 需要哪种产品就调用该种产品的方法,使用方便、容错率高

工厂代码:

public class MulWayNoodlesFactory {

    /**
     * 模仿Executors 类
     * 生产泡面
     *
     * @return
     */
    public static INoodles createPm() {
        return new PaoNoodles();
    }

    /**
     * 模仿Executors 类
     * 生产兰州拉面
     *
     * @return
     */
    public static INoodles createLz() {
        return new LzNoodles();
    }

    /**
     * 模仿Executors 类
     * 生产热干面
     *
     * @return
     */
    public static INoodles createRg() {
        return new ReGanNoodles();
    }
}

测试:

        /**
         * 多方法静态工厂(模仿Executor类)
         */
        System.out.println("========模仿Executor类===============" );
        INoodles lz2 = MulWayNoodlesFactory.createLz();
        lz2.getMessage();

        INoodles rg2 = MulWayNoodlesFactory.createRg();
        rg2.getMessage();

java源码应用

java.util.concurrent.Executors类便是一个生成Executor 的工厂 ,其采用的便是 多方法静态工厂模式

例如ThreadPoolExecutor类构造方法有5个参数,其中三个参数写法固定,前两个参数可配置,如下写。

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

又如JDK想增加创建ForkJoinPool类的方法了,只想配置parallelism参数,便在类里增加一个如下的方法:

 public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

应用场景:

方便创建 同种类型的 复杂参数 对象

2.2、工厂方法模式

工厂方法模式由4部分组成:

  1. 抽象类角色

  2. 具体产品角色

  3. 抽象工厂角色

    public abstract class NoodlesFactory {
       public abstract INoodles create();
    }
    
  4. 具体工厂角色
    public class LzFactory extends NoodlesFactory {
       @Override
       public INoodles create() {
           return new LzNoodles();
       }
    }
    public class PaoFactory extends NoodlesFactory {
       @Override
       public INoodles create() {
           return new PaoNoodles();
       }
    }
    public class ReGanFactory extends NoodlesFactory {
       @Override
       public INoodles create() {
           return new ReGanNoodles();
       }
    }
    

测试:

NoodlesFactory factory1 = new LzFactory();
INoodles lzNoodle = factory1.create();
lzNoodle.getMessage();

与静态工厂类的区别

  1. 不仅仅做出来的产品要抽象, 工厂也应该需要抽象
  2. 工厂方法使一个产品类的实例化延迟到其具体工厂子类.
  3. 工厂方法的好处就是更拥抱变化。当需求变化,只需要增删相应的类,不需要修改已有的类
  4. 而简单工厂需要修改工厂类的create()方法,多方法静态工厂模式需要增加一个静态方法。

缺点:

  • 添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;同时,有更多的类需要编译和运行,会给系统带来一些额外的开销;
  • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
  • 虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类;
  • 一个具体工厂只能创建一种具体产品

应用场景:

  • 当一个类不知道它所需要的对象的类时
    在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可;
  • 当一个类希望通过其子类来指定创建对象时
    在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
  • 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。

2.3、抽象工厂模式

以上介绍的工厂都是单产品系的。抽象工厂是多产品系

举个例子来说,每个店(工厂)不仅仅卖面条,还提供饮料卖。 也就是同时有多个抽象产品,和多个具体产品。

1、第二个抽象产品角色:

public abstract class IDrinks {
    /**
     * 描述每种饮料多少钱
     */
    public abstract void getPrice();
}

2、第二类具体产品角色:

public class ColaDrinks extends IDrinks {
    @Override
    public void prices() {
        System.out.println("可乐三块五");
    }
}
public class WaterDrinks extends IDrinks {
    @Override
    public void prices() {
        System.out.println("白开水不要钱~!");
    }
}

3、抽象工厂:

public abstract class AbstractFoodFactory {
    /**
     * 生产面条
     *
     * @return
     */
    public abstract INoodles createNoodles();

    /**
     * 生产饮料
     */
    public abstract IDrinks createDrinks();
}

4、具体工厂角色,同时生产两种产品:

public class LzlmFoodFactory extends AbstractFoodFactory {
    @Override
    public INoodles createNoodles() {
        return new LzNoodles();//卖兰州拉面
    }

    @Override
    public IDrinks createDrinks() {
        return new WaterDrinks();//卖水
    }
}
public class KFCFoodFactory extends AbstractFoodFactory {
    @Override
    public INoodles createNoodles() {
        return new PaoNoodles();//KFC居然卖泡面
    }

    @Override
    public IDrinks createDrinks() {
        return new ColaDrinks();//卖可乐
    }
}

测试:

   /**
         * 抽象工厂方法:
         */
        System.out.println("==========抽象方法========");
        AbstractFoodFactory abstractFoodFactory1 = new KFCFoodFactory();
        IDrinks cola        = abstractFoodFactory1.createDrinks();
        INoodles paoNoodles = abstractFoodFactory1.createNoodles();

        abstractFoodFactory1= new LzlmFoodFactory();
        IDrinks water     = abstractFoodFactory1.createDrinks();
        INoodles lzNoodle = abstractFoodFactory1.createNoodles();

优点:

  • 分离接口和实现

  客户端使用抽象工厂来创建需要的对象,而客户端根本就不知道具体的实现是谁,客户端只是面向产品的接口编程而已。也就是说,客户端从具体的产品实现中解耦。

  • 使切换产品族变得容易

  因为一个具体的工厂实现代表的是一个产品族,比如上面例子的从Intel系列到AMD系列只需要切换一下具体工厂。

缺点:

但是将工厂也抽象后,有个显著问题,就是类爆炸了。而且每次拓展新产品种类,这需要修改抽象工厂类,因此所有的具体工厂子类,都被牵连,需要同步被修改

使用场景:

  1.一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。

  2.这个系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。

  3.同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。(比如:Intel主板必须使用Intel CPU、Intel芯片组)

  4.系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

工厂模式的起源:

抽象工厂模式的起源或者最早的应用,是用于创建分属于不同操作系统的视窗构建。比如:命令按键(Button)与文字框(Text)都是视窗构建,在UNIX操作系统的视窗环境和Windows操作系统的视窗环境中,这两个构建有不同的本地实现,它们的细节有所不同。

  在每一个操作系统中,都有一个视窗构建组成的构建家族。在这里就是Button和Text组成的产品族。而每一个视窗构件都构成自己的等级结构,由一个抽象角色给出抽象的功能描述,而由具体子类给出不同操作系统下的具体实现。

  可以发现在上面的产品类图中,有两个产品的等级结构,分别是Button等级结构和Text等级结构。同时有两个产品族,也就是UNIX产品族和Windows产品族。UNIX产品族由UNIX Button和UNIX Text产品构成;而Windows产品族由Windows Button和Windows Text产品构成。

    系统对产品对象的创建需求由一个工程的等级结构满足,其中有两个具体工程角色,即UnixFactory和WindowsFactory。UnixFactory对象负责创建Unix产品族中的产品,而WindowsFactory对象负责创建Windows产品族中的产品。这就是抽象工厂模式的应用,抽象工厂模式的解决方案如下图:

 显然,一个系统只能够在某一个操作系统的视窗环境下运行,而不能同时在不同的操作系统上运行。所以,系统实际上只能消费属于同一个产品族的产品。

 在现代的应用中,抽象工厂模式的使用范围已经大大扩大了,不再要求系统只能消费某一个产品族了。因此,可以不必理会前面所提到的原始用意。

3、 总结:

无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为他们之间的演变常常是令人琢磨不透的。经常你会发现,明明使用的工厂方法模式,当新需求来临,稍加修改,加入了一个新方法后,由于类中的产品构成了不同等级结构中的产品族,它就变成抽象工厂模式了;而对于抽象工厂模式,当减少一个方法使的提供的产品不再构成产品族之后,它就演变成了工厂方法模式。

所以,在使用工厂模式时,只需要关心降低耦合度的目的是否达到了