文章目录
- 1 基本介绍
- 2 案例
- 2.1 Drink 抽象类
- 2.2 Tea 类
- 2.3 Coffee 类
- 2.4 DrinkFactory 抽象类
- 2.5 TeaFactory 类
- 2.6 CoffeeFactory 类
- 2.7 Client 类
- 2.8 Client 类运行结果
- 2.9 总结
- 3 各角色之间的关系
- 3.1 角色
- 3.1.1 Product ( 抽象产品 )
- 3.1.2 ConcreteProduct ( 具体产品 )
- 3.1.3 Factory ( 抽象工厂 )
- 3.1.4 ConcreteFactory ( 具体工厂 )
- 3.1.5 Client ( 客户端 )
- 3.2 类图
- 4 注意事项
- 5 在源码中的使用
- 6 优缺点
- 7 适用场景
- 8 总结
1 基本介绍
工厂方法模式(Factory Method Pattern)是一种 创建型 设计模式,它 定义了一个创建对象的 接口(抽象方法),但 让子类决定要实例化的类是哪一个。
它不同于 简单工厂模式,工厂方法模式将对象的创建逻辑分散到各个具体的工厂类中,而不是集中在一个工厂类中。
它还不同于 抽象工厂模式,工厂方法模式在创建产品对象时有 相似的额外操作,所以需要给外界提供一个方法,并强制子类实现另一个方法。
2 案例
本模式的案例 和 简单工厂模式 的案例很相似,只不过将其中的单个工厂改为了 一个抽象工厂类 + 多个工厂实现类。如下所示:
- 饮品:
Drink
抽象类有一个抽象方法drink()
,代表喝这个饮品。它有两个子类:Tea
类和Coffee
类。 - 工厂:
DrinkFactory
抽象类有两个方法:第一个方法是获取已创建的饮品数,第二个方法能够根据饮品的类型生产对应的饮品。它有两个实现类,分别为TeaFactory
类和CoffeeFactory
类。 - 客户端:
Client
类中使用了不同的饮品工厂,创建了不同类型的饮品。
2.1 Drink 抽象类
public abstract class Drink { // 饮品的抽象类
public abstract void drink(); // 喝饮品
protected String name; // 饮品的名称
public Drink(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
2.2 Tea 类
public class Tea extends Drink { // 茶饮品
public Tea(String name) {
super(name);
}
@Override
public void drink() {
System.out.println("你喝了[" + super.name + "]这个茶饮品");
}
}
2.3 Coffee 类
public class Coffee extends Drink { // 咖啡饮品
public Coffee(String name) {
super(name);
}
@Override
public void drink() {
System.out.println("你喝了[" + super.name + "]这个咖啡饮品");
}
}
2.4 DrinkFactory 抽象类
public abstract class DrinkFactory { // 饮品工厂抽象类
private static final AtomicInteger count = new AtomicInteger(0); // 统计创建的饮品个数
// 获取创建的饮品个数
public static int getDrinkCount() {
return count.get();
}
// 创建 指定名称 的饮品,被外部的类调用
public final Drink create(String drinkName) {
count.getAndIncrement();
return createDrink(drinkName);
}
// 创建 指定名称 的饮品,留给子类实现,不应该被外部的类调用
protected abstract Drink createDrink(String drinkName);
}
2.5 TeaFactory 类
public class TeaFactory extends DrinkFactory { // 茶饮品工厂类
@Override
protected Drink createDrink(String drinkName) {
return new Tea(drinkName); // 创建 茶饮品 的对象
}
}
2.6 CoffeeFactory 类
public class CoffeeFactory extends DrinkFactory { // 咖啡饮品工厂类
@Override
protected Drink createDrink(String drinkName) {
return new Coffee(drinkName); // 创建 咖啡饮品 的对象
}
}
2.7 Client 类
// 最好让 Client 与 DrinkFactory 在不同的包下,这样可以避免 Client 误调用 createProduct() 方法
public class Client { // 使用 具体饮品工厂创建具体饮品 的客户端
public static void main(String[] args) {
DrinkFactory teaFactory = new TeaFactory();
Drink tea = teaFactory.create("铁观音");
tea.drink();
DrinkFactory coffeeFactory = new CoffeeFactory();
Drink coffee = coffeeFactory.create("拿铁");
coffee.drink();
System.out.println("已经创建了 " + DrinkFactory.getDrinkCount() + " 个饮品");
}
}
2.8 Client 类运行结果
你喝了[铁观音]这个茶饮品
你喝了[拿铁]这个咖啡饮品
已经创建了 2 个饮品
2.9 总结
在本模式的案例中,工厂类比 简单工厂模式 多;在初始化工厂时,需要初始化具体工厂的对象;在生产产品时,只需要传入与对象有关的参数,不需要传入与对象类型有关的信息。
如果想要新增一种具体产品,则只需写 具体产品类 和 其对应的具体工厂类 的代码,而不需要修改 抽象工厂类,这样就 遵守了开闭原则,提高了系统的扩展性。
此外,本模式还有简单工厂模式的优点——降低系统之间的耦合度,这是因为在本模式中,客户端也不需要知道具体产品的创建逻辑,只要会使用工厂即可。
3 各角色之间的关系
3.1 角色
3.1.1 Product ( 抽象产品 )
该角色负责 定义 所有具体产品的 共性(包括 属性 和 方法),是所有具体产品的 父类 或 被实现的接口。在本案例中,Drink
抽象类扮演了这个角色。
3.1.2 ConcreteProduct ( 具体产品 )
该角色负责 实现 抽象产品所定义的 方法。在本案例中,Tea
类和 Coffee
类都在扮演这个角色。
3.1.3 Factory ( 抽象工厂 )
该角色负责 定义 创建产品对象的 抽象方法,并 统一处理创建产品对象的额外操作。在本案例中,DrinkFactory
抽象类扮演了这个角色。
3.1.4 ConcreteFactory ( 具体工厂 )
该角色负责 实现 创建产品对象的 抽象方法,而且只负责创建某一种具体产品。在本案例中,TeaFactory
类和 CoffeeFactory
类都在扮演这个角色。
3.1.5 Client ( 客户端 )
该角色负责 使用 具体工厂创建具体产品。在本案例中,Client
类扮演了这个角色。
3.2 类图
说明:ConcreteProduct
类和 ConcreteFactory
类是一一对应的,它们两个的关系和 Product
抽象类与 Factory
抽象类的关系是一致的。此外,工厂的 createProduct()
方法的访问权限是 protected
,即仅允许 本包的类 或 子类 使用,并且被 abstract
修饰,强制子类重写此方法;create()
方法的访问权限是 public
,所以可以被外部的类使用。
4 注意事项
本模式的注意事项与 抽象工厂模式 的十分相似,共有以下几点:
- 考虑系统复杂度:使用工厂方法模式需要引入一个或多个工厂类,这可能会增加系统的复杂度,在决定是否使用此模式时,需要权衡其带来的好处和增加的复杂度。
- 共同基类或接口:所有产品类必须有一个共同的基类或接口,以便
create()
方法能够返回一个 通用 的产品类型,这有助于保持系统的灵活性和可扩展性。 - 对象重用:在某些情况下,为了 提高性能 并 减少资源消耗,
create()
方法可能会返回 缓存的对象 或 对象池中的对象,而不是每次都创建新对象,这需要根据具体的应用场景和需求来决定。 - 扩展具体产品:当需要新增产品时,只需新增产品类和相应的工厂类。
- 考虑使用接口:在可能的情况下,将抽象产品角色定义为 接口 而不是 抽象类,可以提高系统的灵活性和扩展性。在 Java 中,一个类只能继承一个父类,但能实现多个接口。
- 考虑使用抽象工厂模式:如果在创建产品对象时有相似的额外操作,则使用本模式比较好,但是,如果没有相似的额外操作,最好使用 抽象工厂模式。
5 在源码中的使用
在 Spring 框架中,工厂模式被广泛应用于对象的创建和管理过程中。
Spring 的 AbstractApplicationContext
抽象类是工厂方法模式的典型应用,它 定义 了多个 getBean()
方法,允许客户端通过传入不同的条件(如 Bean 的名称或类型)来获取对应的 Bean 实例。
// 在 org.springframework.context.support.AbstractApplicationContext 抽象类中有如下几个方法
@Override
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}
@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name, requiredType);
}
@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(requiredType);
}
// 检查 Bean 是否活跃
protected void assertBeanFactoryActive() {
if (!this.active.get()) {
if (this.closed.get()) {
throw new IllegalStateException(getDisplayName()
+ " has been closed already");
} else {
throw new IllegalStateException(getDisplayName()
+ " has not been refreshed yet");
}
}
}
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory()
throws IllegalStateException;
这三个 getBean()
方法都检查 Bean 是否活跃(额外操作),并且还调用了 abstract
方法 getBeanFactory()
,这个方法由其子类实现,例如 AbstractRefreshableApplicationContext
抽象类。
6 优缺点
优点:
- 扩展性好:当需要新增产品时,只需新增产品类和相应的工厂类,无需修改原有的工厂抽象类和工厂类,满足开闭原则。
- 职责单一:每个工厂类只负责创建对应的产品,满足单一职责原则。
- 降低耦合度:客户端通过工厂抽象类与具体产品解耦,不需要知道产品的具体实现细节,只需要知道如何获取所需的产品。
- 灵活性高:可以根据不同的应用场景或条件选择不同的工厂子类来创建产品,增加了系统的灵活性。
缺点:
- 增加了系统的复杂度:每增加一种产品,就需要增加一个对应的产品类和一个对应的工厂子类,这会增加系统中类的数量,从而增加系统的复杂度。
- 增加了系统的抽象层次:工厂方法模式引入了 工厂抽象类 和 工厂子类,增加了系统的抽象层次,使得系统的理解和维护变得 相对 复杂。
- 过度设计:在某些情况下,如果系统中产品的 种类不多,或者产品之间的 差异性不大,使用工厂方法模式可能会导致过度设计,增加了不必要的复杂性。这时使用 简单工厂模式 会好一点。
- 依赖性问题:如果工厂类依赖于其他资源或配置,那么这些依赖项的管理和配置可能会变得复杂。特别是在大型系统中,工厂类的依赖关系可能会形成一个复杂的网络,增加了系统的维护难度。
7 适用场景
- 多种产品族的创建:当系统中存在多个产品族,并且这些产品族之间存在公共的接口或抽象类,但每个产品族的具体实现不同时,可以使用工厂方法模式。每个产品族对应一个工厂类,负责创建该族中的具体产品对象。
- 系统需要扩展但不想修改已有代码:当系统需要添加新的产品类型时,如果希望在不修改已有代码的基础上实现扩展,工厂方法模式是一个很好的选择。
- 复杂的对象创建:当对象的创建过程比较复杂,包含多个步骤或者需要依赖其他对象时,使用工厂方法模式可以将对象的创建过程封装在工厂类中,使得客户端代码更加简洁和易于理解。
- 隐藏具体产品的创建细节:在某些情况下,我们可能不希望客户端代码知道具体产品的创建细节,或者希望 隐藏 这些细节以简化客户端的使用。工厂方法模式可以通过提供一个共同的接口来隐藏具体产品的创建细节,使得客户端只需通过接口来创建对象。
- 使用第三方类库:当系统需要使用第三方类库来创建对象,并且这些对象的创建过程比较复杂或者需要遵循特定的规则时,可以使用工厂方法模式来封装这些创建过程,以便在系统中更加方便地使用这些对象。
- 设计考虑:在系统设计初期,如果预见到将来可能需要添加新的产品类型或者对产品类型进行扩展,可以考虑使用工厂方法模式来为未来可能的扩展 预留接口。这样可以使得系统在扩展时更加灵活和方便。
- 在创建产品对象时有相似的额外操作:如果创建产品对象时有相似的额外操作,那么就使用工厂方法模式,将相似的额外操作放到 提供给外界的 创建产品对象方法中,让工厂子类只实现初始化产品对象的逻辑。
8 总结
工厂方法模式是一种 创建型 设计模式,它通过多个具体工厂类来创建具体产品,这些具体产品通常具有共同的抽象产品作为父类或接口,这些具体工厂类也有共同的抽象工厂作为 父类。
工厂方法模式将具体产品的创建逻辑 封装 在各个工厂类中,并 将相 似的额外操作 封装到对外提供的方法中,客户端无需知道具体产品的类名,只需要知道相应的工厂即可,从而 降低了 客户端与具体产品类之间的 耦合度。
与简单工厂模式相比,工厂方法模式对具体产品的 种类不做限制,并且具体产品的 创建逻辑也可以很复杂 。在扩充产品类型时,该模式不需要修改代码,只需要添加代码,满足 开闭原则。每个具体工厂类只负责创建一种产品,满足 单一职责原则。
与抽象工厂模式相比,工厂方法模式 处理了创建产品对象时相似的额外逻辑,但是在 Java 中,由于单继承,所以在 灵活性 方面,使用抽象类(工厂方法模式) 比 使用接口(抽象工厂模式) 差一些。