文章目录
- 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 基本介绍
抽象工厂模式(Abstract Factory 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);}@Overridepublic void drink() {System.out.println("你喝了[" + super.name + "]这个茶饮品");}
}
2.3 Coffee 类
public class Coffee extends Drink { // 咖啡饮品public Coffee(String name) {super(name);}@Overridepublic void drink() {System.out.println("你喝了[" + super.name + "]这个咖啡饮品");}
}
2.4 DrinkFactory 接口
public interface DrinkFactory { // 饮品工厂接口Drink createDrink(String drinkName); // 创建 指定名称 的饮品
}
2.5 TeaFactory 类
public class TeaFactory implements DrinkFactory { // 茶饮品工厂类@Overridepublic Drink createDrink(String drinkName) {return new Tea(drinkName); // 创建 茶饮品 的对象}
}
2.6 CoffeeFactory 类
public class CoffeeFactory implements DrinkFactory { // 咖啡饮品工厂类@Overridepublic Drink createDrink(String drinkName) {return new Coffee(drinkName); // 创建 咖啡饮品 的对象}
}
2.7 Client 类
public class Client { // 使用 具体饮品工厂创建具体饮品 的客户端public static void main(String[] args) {DrinkFactory teaFactory = new TeaFactory();Drink tea = teaFactory.createDrink("铁观音");tea.drink();DrinkFactory coffeeFactory = new CoffeeFactory();Drink coffee = coffeeFactory.createDrink("拿铁");coffee.drink();}
}
2.8 Client 类运行结果
你喝了[铁观音]这个茶饮品
你喝了[拿铁]这个咖啡饮品
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
接口的关系是一致的。
4 注意事项
本模式的注意事项与 工厂方法模式 的十分相似,共有以下几点:
- 考虑系统复杂度:使用抽象工厂模式需要引入一个或多个工厂类,这可能会增加系统的复杂度,在决定是否使用此模式时,需要权衡其带来的好处和增加的复杂度。
- 共同基类或接口:所有产品类必须有一个共同的基类或接口,以便
createProduct()
方法能够返回一个 通用 的产品类型,这有助于保持系统的灵活性和可扩展性。 - 对象重用:在某些情况下,为了 提高性能 并 减少资源消耗,
createProduct()
方法可能会返回 缓存的对象 或 对象池中的对象,而不是每次都创建新对象,这需要根据具体的应用场景和需求来决定。 - 扩展具体产品:当需要新增产品时,只需新增产品类和相应的工厂类。
- 考虑使用接口:在可能的情况下,将抽象产品角色定义为 接口 而不是 抽象类,可以提高系统的灵活性和扩展性。在 Java 中,一个类只能继承一个父类,但能实现多个接口。
- 考虑使用工厂方法模式:如果在创建产品对象时没有相似的额外操作,则使用本模式比较好;如果有相似的额外操作,最好使用 工厂方法模式。
5 在源码中的使用
在 Spring 框架中,工厂模式被广泛应用于对象的创建和管理过程中。
Spring 的 BeanFactory
接口是抽象工厂模式的典型应用,它 定义 了多个 getBean()
方法,允许客户端通过传入不同的条件(如 Bean 的名称或类型)来获取对应的 Bean 实例。
// 在 org.springframework.beans.factory.BeanFactory 接口中有如下三个方法
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
这三个方法都没有被实现,只是被定义在 BeanFactory
接口中 ,具体实现交给其实现类,比如 AbstractBeanFactory
抽象类。
6 优缺点
优点:
- 扩展性好:当需要新增产品时,只需新增产品类和相应的工厂类,无需修改原有的工厂接口和工厂类,满足开闭原则。
- 职责单一:每个工厂类只负责创建对应的产品,满足单一职责原则。
- 降低耦合度:客户端通过工厂接口与具体产品解耦,不需要知道产品的具体实现细节,只需要知道如何获取所需的产品。
- 灵活性高:可以根据不同的应用场景或条件选择不同的工厂实现类来创建产品,增加了系统的灵活性。
缺点:
- 增加了系统的复杂度:每增加一种产品,就需要增加一个对应的产品类和一个对应的工厂实现类,这会增加系统中类的数量,从而增加系统的复杂度。
- 增加了系统的抽象层次:抽象工厂模式引入了 工厂接口 和 工厂实现类,增加了系统的抽象层次,使得系统的理解和维护变得 相对 复杂。
- 过度设计:在某些情况下,如果系统中产品的 种类不多,或者产品之间的 差异性不大,使用抽象工厂模式可能会导致过度设计,增加了不必要的复杂性。这时使用 简单工厂模式 会好一点。
- 依赖性问题:如果工厂类依赖于其他资源或配置,那么这些依赖项的管理和配置可能会变得复杂。特别是在大型系统中,工厂类的依赖关系可能会形成一个复杂的网络,增加了系统的维护难度。
7 适用场景
- 多种产品族的创建:当系统中存在多个产品族,并且这些产品族之间存在公共的接口或抽象类,但每个产品族的具体实现不同时,可以使用工厂方法模式。每个产品族对应一个工厂类,负责创建该族中的具体产品对象。
- 系统需要扩展但不想修改已有代码:当系统需要添加新的产品类型时,如果希望在不修改已有代码的基础上实现扩展,工厂方法模式是一个很好的选择。
- 复杂的对象创建:当对象的创建过程比较复杂,包含多个步骤或者需要依赖其他对象时,使用工厂方法模式可以将对象的创建过程封装在工厂类中,使得客户端代码更加简洁和易于理解。
- 隐藏具体产品的创建细节:在某些情况下,我们可能不希望客户端代码知道具体产品的创建细节,或者希望 隐藏 这些细节以简化客户端的使用。工厂方法模式可以通过提供一个共同的接口来隐藏具体产品的创建细节,使得客户端只需通过接口来创建对象。
- 使用第三方类库:当系统需要使用第三方类库来创建对象,并且这些对象的创建过程比较复杂或者需要遵循特定的规则时,可以使用工厂方法模式来封装这些创建过程,以便在系统中更加方便地使用这些对象。
- 设计考虑:在系统设计初期,如果预见到将来可能需要添加新的产品类型或者对产品类型进行扩展,可以考虑使用工厂方法模式来为未来可能的扩展 预留接口。这样可以使得系统在扩展时更加灵活和方便。
8 总结
抽象工厂模式是一种 创建型 设计模式,它通过多个具体工厂类来创建具体产品,这些具体产品通常具有共同的抽象产品作为父类或接口,这些具体工厂类也有共同的抽象工厂作为 接口。
抽象工厂模式将具体产品的创建逻辑 封装 在各个工厂类中,客户端无需知道具体产品的类名,只需要知道相应的工厂即可,从而 降低了 客户端与具体产品类之间的 耦合度。
与简单工厂模式相比,抽象工厂模式对具体产品的 种类不做限制,并且具体产品的 创建逻辑也可以很复杂 。在扩充产品类型时,该模式不需要修改代码,只需要添加代码,满足 开闭原则。每个具体工厂类只负责创建一种产品,满足 单一职责原则。
与工厂方法模式相比,抽象工厂模式 没有处理创建产品对象时相似的额外逻辑,但是在 Java 中,由于单继承,所以在 灵活性 方面,使用接口(抽象工厂模式) 比 使用抽象类(工厂方法模式) 好一些。