简单工厂其实不是一个标准的的设计模式。GOF 23 种设计模式中只有「工厂方法模式」与「抽象工厂模式」。简单工厂模式可以看为工厂方法模式的一种特例,为了统一整理学习,就都归为工厂模式。
简单工厂模式、工厂方法模式、抽象工厂模式在设计模式的分类中都属于创建型模式,三种模式从越来越抽象。
创建型模式
- 创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。
- 创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。
- 工厂模式是创建型模式中比较重要的。工厂模式的主要功能就是帮助我们实例化对象。之所以名字中包含工厂模式四个字,是因为对象的实例化过程是通过工厂实现的,是用工厂代替 new 操作的。
工厂模式的优点:
- 可以使代码结构清晰,有效地封装变化。在编程中,产品类的实例化有时候是比较复杂和多变的,通过工厂模式,将产品的实例化封装起来,使得调用者根本无需关心产品的实例化过程,只需依赖工厂即可得到自己想要的产品。
- 对调用者屏蔽具体的产品类。如果使用工厂模式,调用者只关心产品的接口就可以了,至于具体的实现,调用者根本无需关心。即使变更了具体的实现,对调用者来说没有任何影响。
- 降低耦合度。产品类的实例化通常来说是很复杂的,它需要依赖很多的类,而这些类对于调用者来说根本无需知道,如果使用了工厂方法,我们需要做的仅仅是实例化好产品类,然后交给调用者使用。对调用者来说,产品所依赖的类都是透明的。
使用场景:
- 作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
- 工厂模式是一种典型的解耦模式,迪米特法则在工厂模式中表现的尤为明显。假如调用者自己组装产品需要增加依赖关系时,可以考虑使用工厂模式。将会大大降低对象之间的耦合度。
- 由于工厂模式是依靠抽象架构的,它把实例化产品的任务交由实现类完成,扩展性比较好。也就是说,当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。
(1)简单工厂模式
- 简单工厂SimpleFactory:负责创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
- 抽象产品IProduct:简单工厂创建的共有对象的父类,负责描述所有实例共有的公共接口。
- 具体产品ConcreteProduct:简单工厂模式的创建目标。
UML类图:
简单工厂模式的通用写法:
抽象产品类IProduct:
public interface IProuduct {
void doSomeThing();
}
具体产品类ProductA:
@Slf4j
public class ProductA implements IProuduct {
@Override
public void doSomeThing() {
log.info("我是ProductA");
}
}
具体产品类ProductB:
@Slf4j
public class ProductB implements IProuduct {
@Override
public void doSomeThing() {
log.info("我是ProductB");
}
}
简单工厂类SimpleFactory(通过传入的productName来决定生成哪个具体产品)
public class SimpleFactory {
static IProuduct makeProduct(String productName) {
if ("ProductA".equals(productName)) {
return new ProductA();
} else if ("ProductB".equals(productName)) {
return new ProductB();
} else {
return null;
}
}
}
客户端调用:
public class Client {
public static void main(String[] args) {
// 生成产品B
IProuduct product = SimpleFactory.makeProduct("ProductB");
product.doSomeThing();
}
}
在简单工厂模式中,抽象产品既可以是各个具体产品类实现的共同的接口,也可以是各个具体产品类继承的抽象父类。
优点:
-
简单工厂模式,封装了创建对象的逻辑,完成了创建对象逻辑与业务代码逻辑的解耦。试想客户端是多个service层的文件,对比不使用简单工厂模式,当我们要改变产生对象的逻辑时,需要在多个service文件中找到这部分代码进行修改。在使用简单工厂模式后,只需要修改简单工厂中生成对象的逻辑即可,不需要修改业务代码。完成了解耦。
缺点:
- 每当具体产品类的抽象产品类增多时,会需要在简单工厂类中新增关于新增产品类对象生成的方法。当抽象产品类很多时,抽象工厂会很臃肿。并且在这种情形下,SimpleFactory类也不符合开闭原则。
(2)工厂方法模式
工厂方法模式:定义创建对象的接口,但具体实现放到实现这个接口的管理具体一类产品的对象生成的工厂类中去实现。
工厂方法模式的角色:
- 抽象工厂:定义了创建抽象产品的方法,任何在模式中创建对象的工厂类都必须实现这个接口;
- 具体工厂:实现抽象工厂接口的具体工厂类,负责生产具体的产品;
- 抽象产品:工厂方法模式所创建的对象的超类型。也是产品对象的共同父类或者共同拥有的接口;
- 具体产品:实现了抽象产品角色所定义的接口。某具体产品由具体工厂创建,他们往往一一对应。
UML类图:
工厂方法模式的通用写法:
抽象工厂:
/**
* 抽象工厂
*/
public interface IFactory {
IProduct makeProduct();
}
抽象产品:
/**
* 抽象产品
*/
public interface IProduct {
void doSomeThing();
}
具体产品ProductA:
/**
* 具体产品A
*/
@Slf4j
public class ProductA implements IProduct {
@Override
public void doSomeThing() {
log.info("我是productA");
}
}
具体产品ProductB:
/**
* 具体产品B
*/
@Slf4j
public class ProductB implements IProduct {
@Override
public void doSomeThing() {
log.info("我是ProductB");
}
}
生产ProductA的具体工厂FactoryA:
/**
* 生产ProductA的具体工厂
*/
public class FactoryA implements IFactory {
@Override
public IProduct makeProduct() {
return new ProductA();
}
}
生产ProductB的具体工厂FactoryB:
/**
* 生产ProductB的具体工厂
*/
public class FactoryB implements IFactory {
@Override
public IProduct makeProduct() {
return new ProductB();
}
}
客户端代码:
public class Client {
public static void main(String[] arges) {
// 生产ProductA
FactoryA factoryA = new FactoryA();
factoryA.makeProduct().doSomeThing();
}
}
适用场景:
工厂方法模式和简单工厂模式虽然都是通过工厂来创建对象,他们之间最大的不同是——工厂方法模式在设计上完全完全符合“开闭原则”。
-
一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
- 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
使用场景
- 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
- 数据库访问:当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
- 设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
缺点:
- 类的个数容易过多,增加复杂度;
- 实现抽象工厂接口的具体工厂只能生产出一种产品(可以用抽象工厂模式解决)。
(3)抽象工厂
抽象工厂模式在工厂方法模式的基础上进行进一步抽象。设想下面这种场景:
现有两种具体产品(具体产品):篮球,足球(在此基础上的抽象产品可看成球)。同时,对于足球和篮球来说,他们都有两种品牌安踏、李宁。
如果采用工厂方法模式解决上述场景中创建产品的问题,需要在抽象工厂中定义创建产品的方法,并新建四个用于创建具体产品的具体工厂类:用于创建“李宁篮球”的具体工厂,用于创建“李宁足球”的具体工厂,用于创建“安踏篮球”的具体工厂,用于创建“安踏足球”的具体工厂。
从上面的解决方式可以看出:使用工厂方法模式,创建了很多具体工厂类,而没有利用产品的“商品族”的概念。
由此引出,抽象工厂模式是用于解决“一类产品”的创建问题(在上述场景中,可以把“李宁篮球”,“李宁足球”,“安踏篮球”,“安踏足球”归纳为:“篮球”,“足球”这两类商品)。
抽象工厂模式的角色:
- 抽象工厂:声明创建抽象产品对象的一个接口(有几个产品组,则声明几个方法。比如对于上述的场景,需要声明一个用于生产篮球类产品的方法,还需要声明一个用于生产足球类产品的方法);
- 具体工厂:实现创建具体产品对象的操作;
- 抽象产品:为一类产品对象的抽象;
- 具体产品:定义一个将被相应的具体工厂创建的产品对象(比如上述场景中的:李宁篮球);
UML类图:
抽象工厂的通常写法:
抽象工厂:
/**
* 抽象工厂
*/
public interface AbstractFactory {
Basketball makeBasketball();
Football makeFootball();
}
抽象产品族篮球:
/**
* 抽象产品族;篮球
*/
public interface Basketball {
void sayBasketball();
}
抽象产品族足球:
/**
* 抽象产品族:足球
*/
public interface Football {
void sayFootball();
}
四个具体产品:李宁篮球、李宁足球、安踏篮球、安踏足球
/**
* 具体产品:李宁篮球
*/
@Slf4j
public class LiningBasketball implements Basketball {
@Override
public void sayBasketball() {
log.info("我是李宁篮球");
}
}
/**
* 具体产品:李宁足球
*/
@Slf4j
public class LiningFootball implements Football {
@Override
public void sayFootball() {
log.info("我是李宁足球");
}
}
/**
* 具体产品:安踏篮球
*/
@Slf4j
public class AntaBasketball implements Basketball {
@Override
public void sayBasketball() {
log.info("我是安踏篮球");
}
}
/**
* 具体产品:安踏足球
*/
@Slf4j
public class AntaFootball implements Football {
@Override
public void sayFootball() {
log.info("我是安踏足球");
}
}
具体工厂:
/**
* 具体工厂,负责生产李宁篮球,李宁足球
*/
public class LiningFactoy implements AbstractFactory {
@Override
public Basketball makeBasketball() {
return new LiningBasketball();
}
@Override
public Football makeFootball() {
return new LiningFootball();
}
}
/**
* 具体工厂,负责生产安踏篮球,安踏足球
*/
public class AntaFactory implements AbstractFactory {
@Override
public Basketball makeBasketball() {
return new AntaBasketball();
}
@Override
public Football makeFootball() {
return new AntaFootball();
}
}
客户端代码:
public class Client {
public static void main(String[] args){
// 生产李宁篮球和安踏足球
LiningFactoy liningFactoy = new LiningFactoy();
AntaFactory antaFactory = new AntaFactory();
liningFactoy.makeBasketball().sayBasketball();
antaFactory.makeFootball().sayFootball();
}
}
抽象工厂模式适用场景
抽象工厂模式和工厂方法模式一样,都符合开闭原则。但是不同的是,工厂方法模式在增加一个具体产品的时候,都要增加对应的工厂。但是抽象工厂模式只有在新增一个类型的具体产品时才需要新增工厂。也就是说,工厂方法模式的一个工厂只能创建一个具体产品。而抽象工厂模式的一个工厂可以创建属于一类类型的多种具体产品。工厂创建产品的个数介于简单工厂模式和工厂方法模式之间。
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
- 系统中有多于一个的产品族,而每次只使用其中某一产品族。
- 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
- 系统结构稳定,不会频繁的增加对象。
抽象工厂模式总结
抽象工厂模式是工厂方法模式的进一步延伸,由于它提供了功能更为强大的工厂类并且具备较好的可扩展性,在软件开发中得以广泛应用,尤其是在一些框架和 API 类库的设计中,例如在 Java 语言的 AWT(抽象窗口工具包)中就使用了抽象工厂模式,它使用抽象工厂模式来实现在不同的操作系统中应用程序呈现与所在操作系统一致的外观界面。抽象工厂模式也是在软件开发中最常用的设计模式之一。
优点:
-
抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
-
当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
-
增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:
- 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。
工厂模式的退化:
当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。