工厂方法模式
定义一个用于创建对象的 接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
/**
* 抽象产品类
*
* @author gcl
* @date 2020-08-13 0:06
*/
public abstract class Product {
// 产品类的公共方法
public void method1() {
//业务处理逻辑
}
// 抽象方法
public abstract void method2();
}
/**
* 抽象工厂类
*
* @author gcl
* @date 2020-08-13 0:09
*/
public abstract class Creator {
/**
* 创建一个产品对象,输入参数可自行设置
*
* @param c class
* @param <T> Product接口的子类
* @return 具体产品对象
*/
public abstract <T extends Product> T createProduct(Class<T> c);
}
/**
* 具体工厂类
*
* @author gcl
* @date 2020-08-13 0:11
*/
public class ConcreteCreator extends Creator {
@Override
public <T extends Product> T createProduct(Class<T> c) {
Product product = null;
try {
product = (Product) Class.forName(c.getName()).newInstance();
} catch (Exception e) {
}
return (T) product;
}
}
/**
* 具体产品类
*
* @author gcl
* @date 2020-08-13 0:08
*/
public class ConcreteProduct1 extends Product {
@Override
public void method2() {
}
}
/**
* 具体产品类
*
* @author gcl
* @date 2020-08-13 0:08
*/
public class ConcreteProduct2 extends Product {
@Override
public void method2() {
}
}
/**
* 场景类
*
* @author gcl
* @date 2020-08-13 0:13
*/
public class Client {
public static void main(String[] args) {
ConcreteCreator creator = new ConcreteCreator();
ConcreteProduct1 product = creator.createProduct(ConcreteProduct1.class);
//业务处理
}
}
优点
- 良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需 要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创 建对象的艰辛过程,降低模块间的耦合。
- 工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体 的工厂类或扩展一个工厂类,就可以完成“拥抱变化”。
- 屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关 心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。
- 工厂方法模式是典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实 现类都不用关心,符合迪米特法则,我不需要的就不要去交流;也符合依赖倒置原则,只依 赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类,没问题!
使用场景
- 工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以 使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
- 需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。
- 工厂方法模式可以用在异构项目中
- 可以使用在测试驱动开发的框架下。例如,测试一个类A,就需要把与类A有关 联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B 的耦合。目前由于JMock和EasyMock的诞生,该使用场景已经弱化了。
工厂方法模式的扩展
缩小为简单工厂模式
将工厂类的方法定义为static方法。
升级为多个工厂类
每一个产品类都对应了一个工厂类,好处就是创建类的职责清晰,而且结构简单,但是给可扩展性和可维护性带来了一定的影响。
替代单例模式
public class Singleton {
private Singleton(){}
public void doSomething(){}
}
/**
* 负责生成单例的工厂类<p>
* 通过获得类构造器,然后设置访问权限,生成一个对象,然后提供外部访问,保证内存中的对象唯一
* @author gcl
* @date 2020-08-13 0:42
*/
public class SingletonFactory {
private static Singleton singleton;
static {
try {
Class clazz = Class.forName(Singleton.class.getName());
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
singleton = (Singleton) constructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Singleton getSingleton() {
return singleton;
}
}
延迟初始化
一个对象被消费完毕后,并不立刻释放,工厂类 保持其初始状态,等待再次被使用。
ProductFactory负责产品类对象的创建工作,并且通过prMap变量产生一个缓存,对需要 再次被重用的对象保留。例如spring的IOC容器,Mybatis的一级缓存,都是使用类似的方式。
/**
*
* 延迟加载的工厂类 <p>
* 代码还比较简单,通过定义一个Map容器,容纳所有产生的对象,如果在Map容器中已经有的对象,则直接取出返回;如果没有,<p>
* 则根据需要的类型产生一个对象并放入到Map容器中,以方便下次调用。<p>
* @author gcl
* @date 2020-08-14 21:28
*/
public class LazyInitFactory {
private static final Map<String, Product> PRODUCT_MAP = new HashMap<>();
public static synchronized Product createProduct(String type){
Product product = null;
if (PRODUCT_MAP.containsKey(type)){
product = PRODUCT_MAP.get(type);
}else{
if ("Product1".equals(type)){
product = new ConcreteProduct1();
}else{
product = new ConcreteProduct2();
}
PRODUCT_MAP.put(type,product);
}
return product;
}
}
抽象工厂模式
为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们 的具体类。
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象 工厂模式产生需要的对象是一种非常好的解决方式。
public abstract class AbstractCreator {
/**
* A产品家族
*
* @return AbstractProductA
*/
public abstract AbstractProductA createProductA();
/**
* B产品家族
*
* @return AbstractProductB
*/
public abstract AbstractProductB createProductB();
}
注意:有N个产品族,在抽象工厂类中就应该有N个创建方法。
public abstract class AbstractProductA {
/**
* 产品共有方法
*/
public void shareMethod() {
}
/**
* 每个产品相同方法,不同实现
*/
public abstract void doSomething();
}
public abstract class AbstractProductB {
/**
* 产品共有方法
*/
public void shareMethod() {
}
/**
* 每个产品相同方法,不同实现
*/
public abstract void doSomething();
}
public class Creator1 extends AbstractCreator {
@Override
public AbstractProductA createProductA() {
return new ProductA1();
}
@Override
public AbstractProductB createProductB() {
return new ProductB1();
}
}
public class Creator2 extends AbstractCreator {
@Override
public AbstractProductA createProductA() {
return new ProductA2();
}
@Override
public AbstractProductB createProductB() {
return new ProductB2();
}
}
public class ProductA1 extends AbstractProductA {
@Override
public void doSomething() {
System.out.println("product A1 implement");
}
}
public class ProductA2 extends AbstractProductA {
@Override
public void doSomething() {
System.out.println("product A2 implement");
}
}
public class ProductB1 extends AbstractProductB {
@Override
public void doSomething() {
System.out.println("product B1 implement");
}
}
public class ProductB2 extends AbstractProductB {
@Override
public void doSomething() {
System.out.println("product B2 implement");
}
}
注意:有M个产品等级就应该有M个实现工厂类,在每个实现工厂中,实现不同产品族 的生产任务。
/**
* 一个模式在什么情况下才能够使用,是很多读者比较困惑的地方。</br>
* 抽象工厂模式是一个简单的模式,使用的场景非常多,大家在软件产品开发过程中,涉及不同操作系统的时候,
* 都可以考虑使用抽象工厂模式,例如一个应用,需要在三个不同平台(Windows、Linux、Android(Google发布的智能终端操作系统))上运行,
* 你会怎么设计?分别设计三套不同的应用?非也,通过抽象工厂模式屏蔽掉操作系统对应用的影响。
* 三个不同操作系统上的软件功能、应用逻辑、UI都应该是非常类似的,唯一不同的是调用不同的工厂方法,
* 由不同的产品类去处理与操作系统交互的信息。
* @author gcl
* @date 2020-09-21 15:51
*/
public class Client {
public static void main(String[] args) {
// 两个工厂
AbstractCreator creator1 = new Creator1();
AbstractCreator creator2 = new Creator2();
// 产生A1
AbstractProductA productA1 = creator1.createProductA();
// 产生B1
AbstractProductB productB1 = creator1.createProductB();
// 产生A2
AbstractProductA productA2 = creator2.createProductA();
// 产生B2
AbstractProductB productB2 = creator2.createProductB();
// 业务代码
}
}
在场景类中,没有任何一个方法与实现类有关系,对于一个产品来说,我们只要知道它 的工厂方法就可以直接产生一个产品对象,无须关心它的实现类。
优点
- 封装性,每个产品的实现类不是高层模块要关心的,它要关心的是接口,是抽象,它不关心对象是如何创建出来。由工厂类负责,只要知道工厂类是谁,就能创建出一个需要的对象,省时省力,优秀设计就应该如此。
- 产品族内的约束为非公开状态。
缺点
抽象工厂模式的最大缺点就是产品族扩展非常困难。我们以通用代码 为例,如果要增加一个产品C,也就是说产品家族由原来的2个增加到3个,看看我们的程序有多大改动吧!抽象类AbstractCreator要增加一个方法createProductC(),然后两个实现类都要修改。严重违反了开闭原则,而且我们一直说明抽象类和接口是一个契约。改变契约,所有与契约有关系的代码都要修改。
使用场景
一个对象族(或是一组没有任何关系的对象) 都有相同的约束,则可以使用抽象工厂模式。例如一个文本编辑器和一个图片处理器,都是软件实体,但是Linux下的文本编辑器和Windows下的文本编辑器虽然功能和界 面都相同,但是代码实现是不同的,图片处理器也有类似情况。也就是具有了共同的约束条件:操作系统类型。于是我们可以使用抽象工厂模式,产生不同操作系统下的编辑器和图片处理器。
注意事项
在抽象工厂模式的缺点中,我们提到抽象工厂模式的产品族扩展比较困难,但是一定要清楚,是产品族扩展困难,而不是产品等级。在该模式下,产品等级是非常容易扩展的,增加一个产品等级,只要增加一个工厂类负责新增加出来的产品生产任务即可。也就是说横向扩展容易,纵向扩展困难。