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();
/*输出 热干面*/
特点
- 它是一个具体的类,非接口 抽象类。有一个重要的create()方法,利用if或者 switch创建产品并返回。
- create()方法通常是静态的,所以也称之为静态工厂。
缺点
- 扩展性差,我想增加一种面条,除了新增一个面条产品类,还需要修改工厂类方法,违反了开闭原则。
- 不同的产品需要不同额外参数的时候 不支持。
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部分组成:
- 抽象类角色
-
具体产品角色
-
抽象工厂角色
public abstract class NoodlesFactory { public abstract INoodles create(); }
- 具体工厂角色
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();
与静态工厂类的区别
- 不仅仅做出来的产品要抽象, 工厂也应该需要抽象
- 工厂方法使一个产品类的实例化延迟到其具体工厂子类.
- 工厂方法的好处就是更拥抱变化。当需求变化,只需要增删相应的类,不需要修改已有的类。
- 而简单工厂需要修改工厂类的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、 总结:
无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为他们之间的演变常常是令人琢磨不透的。经常你会发现,明明使用的工厂方法模式,当新需求来临,稍加修改,加入了一个新方法后,由于类中的产品构成了不同等级结构中的产品族,它就变成抽象工厂模式了;而对于抽象工厂模式,当减少一个方法使的提供的产品不再构成产品族之后,它就演变成了工厂方法模式。
所以,在使用工厂模式时,只需要关心降低耦合度的目的是否达到了。