文章目录
- 一、概述
- 二、实现方式
-
- [2.1 结构与角色](#2.1 结构与角色)
- [2.2 代码实现](#2.2 代码实现)
- [2.3 扩展新的产品族](#2.3 扩展新的产品族)
- [2.4 扩展新的产品等级(困难)](#2.4 扩展新的产品等级(困难))
- [三、工厂方法 vs 抽象工厂](#三、工厂方法 vs 抽象工厂)
-
- [3.1 本质区别](#3.1 本质区别)
- [3.2 角色对比](#3.2 角色对比)
- [3.3 关系说明](#3.3 关系说明)
- 四、总结
一、概述
在上一篇博客 设计模式-工厂方法模式 中,我们了解了工厂方法模式如何通过将对象的创建延迟到子类来实现客户端与具体产品的解耦。工厂方法模式针对的是一种产品的创建,即一个工厂只负责创建一种产品。
但在实际开发中,我们经常会遇到这样的场景:一个系统需要创建多个相关的产品,而这些产品之间有着内在的联系,必须保证它们属于同一品牌或同一系列。比如一个家具店,需要同时提供椅子和沙发,而椅子和沙发必须风格统一------要么都是现代风格,要么都是古典风格。如果用工厂方法模式,每种产品都需要一个独立的工厂,但这样很难保证多个产品之间的风格一致性。
这时候就需要用到抽象工厂模式了。
抽象工厂模式(Abstract Factory)提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它是工厂方法模式的升级版本,工厂方法模式只生产一种产品,而抽象工厂模式可以生产多种产品(即一个产品族)。
产品族与产品等级:
- 产品等级:即产品的继承结构,比如咖啡是一个产品等级,甜品也是一个产品等级
- 产品族:由同一个工厂生产的、位于不同产品等级的一组产品,比如星巴克品牌的咖啡和甜品就是一个产品族
核心:同一个工厂生产的多种产品必须属于同一产品族,保证风格一致
二、实现方式
2.1 结构与角色
抽象工厂模式包含以下角色:
创建
创建
实现
实现
创建
创建
创建
创建
实现
实现
实现
实现
Client 客户端
AbstractFactory 抽象工厂
AbstractProductA 抽象产品A
AbstractProductB 抽象产品B
ConcreteFactory1 具体工厂1
ConcreteFactory2 具体工厂2
ConcreteProductA1 产品A1
ConcreteProductB1 产品B1
ConcreteProductA2 产品A2
ConcreteProductB2 产品B2
- AbstractFactory(抽象工厂):声明了一组用于创建产品族的方法,每个方法对应一种产品
- ConcreteFactory(具体工厂):实现抽象工厂接口,创建具体的产品族对象
- AbstractProduct(抽象产品):定义产品的公共接口,每种产品对应一个抽象产品
- ConcreteProduct(具体产品):具体的产品实现,属于某个产品族
2.2 代码实现
以咖啡甜品店为例,咖啡店不仅提供咖啡,还提供甜品,并且有两种风格的搭配:美式风格和意式风格。美式风格搭配美式咖啡 + 抹茶慕斯,意式风格搭配拿铁咖啡 + 提拉米苏。
(1)抽象产品------咖啡
java
/**
* 抽象产品:咖啡
*/
public abstract class Coffee {
/**
* 获取咖啡名称
*/
public abstract String getName();
/**
* 加奶
*/
public void addMilk() {
System.out.println("加奶...");
}
}
(2)抽象产品------甜品
java
/**
* 抽象产品:甜品
*/
public abstract class Dessert {
/**
* 获取甜品名称
*/
public abstract String getName();
/**
* 展示甜品
*/
public void show() {
System.out.println("展示甜品:" + getName());
}
}
(3)具体产品------美式咖啡
java
/**
* 具体产品:美式咖啡
*/
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
(4)具体产品------拿铁咖啡
java
/**
* 具体产品:拿铁咖啡
*/
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
(5)具体产品------抹茶慕斯
java
/**
* 具体产品:抹茶慕斯
*/
public class MatchaMousse extends Dessert {
@Override
public String getName() {
return "抹茶慕斯";
}
}
(6)具体产品------提拉米苏
java
/**
* 具体产品:提拉米苏
*/
public class Tiramisu extends Dessert {
@Override
public String getName() {
return "提拉米苏";
}
}
(7)抽象工厂------甜品店工厂
java
/**
* 抽象工厂:甜品店工厂
* 定义了创建咖啡和甜品两个产品的接口
*/
public interface DessertFactory {
/**
* 创建咖啡
*
* @return 咖啡对象
*/
Coffee createCoffee();
/**
* 创建甜品
*
* @return 甜品对象
*/
Dessert createDessert();
}
(8)具体工厂------美式风格工厂
java
/**
* 具体工厂:美式风格工厂
* 美式咖啡 + 抹茶慕斯(美式风格产品族)
*/
public class AmericanDessertFactory implements DessertFactory {
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
@Override
public Dessert createDessert() {
return new MatchaMousse();
}
}
(9)具体工厂------意式风格工厂
java
/**
* 具体工厂:意式风格工厂
* 拿铁咖啡 + 提拉米苏(意式风格产品族)
*/
public class ItalyDessertFactory implements DessertFactory {
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
@Override
public Dessert createDessert() {
return new Tiramisu();
}
}
(10)客户端调用
java
public class AbstractFactoryDemo {
public static void main(String[] args) {
// 美式风格
DessertFactory americanFactory = new AmericanDessertFactory();
Coffee americanCoffee = americanFactory.createCoffee();
Dessert matchaMousse = americanFactory.createDessert();
System.out.println("美式风格搭配:" + americanCoffee.getName() + " + " + matchaMousse.getName());
// 美式风格搭配:美式咖啡 + 抹茶慕斯
// 意式风格
DessertFactory italyFactory = new ItalyDessertFactory();
Coffee latteCoffee = italyFactory.createCoffee();
Dessert tiramisu = italyFactory.createDessert();
System.out.println("意式风格搭配:" + latteCoffee.getName() + " + " + tiramisu.getName());
// 意式风格搭配:拿铁咖啡 + 提拉米苏
}
}
从客户端代码可以看出,我们只需要选择对应的工厂,就能获取到风格统一的咖啡和甜品搭配,而不用分别去创建咖啡和甜品,再手动确保它们风格一致。
2.3 扩展新的产品族
假如咖啡店需要新增一种法式风格的搭配:法式咖啡 + 马卡龙,使用抽象工厂模式只需要新增产品类和对应的工厂类即可:
java
/**
* 具体产品:法式咖啡
*/
public class FrenchCoffee extends Coffee {
@Override
public String getName() {
return "法式咖啡";
}
}
java
/**
* 具体产品:马卡龙
*/
public class Macaron extends Dessert {
@Override
public String getName() {
return "马卡龙";
}
}
java
/**
* 具体工厂:法式风格工厂
*/
public class FrenchDessertFactory implements DessertFactory {
@Override
public Coffee createCoffee() {
return new FrenchCoffee();
}
@Override
public Dessert createDessert() {
return new Macaron();
}
}
客户端使用:
java
// 法式风格
DessertFactory frenchFactory = new FrenchDessertFactory();
Coffee frenchCoffee = frenchFactory.createCoffee();
Dessert macaron = frenchFactory.createDessert();
System.out.println("法式风格搭配:" + frenchCoffee.getName() + " + " + macaron.getName());
// 法式风格搭配:法式咖啡 + 马卡龙
新增产品族只需新增产品类和对应工厂类,无需修改已有的代码,符合开闭原则。
2.4 扩展新的产品等级(困难)
但是,如果咖啡店需要新增一个产品等级------冰淇淋,就需要修改抽象工厂接口,增加 createIceCream() 方法,这会导致所有已有的具体工厂类都需要修改,违背了开闭原则。
java
/**
* 新增抽象产品:冰淇淋
*/
public abstract class IceCream {
public abstract String getName();
}
java
// 修改抽象工厂接口,增加创建冰淇淋的方法
public interface DessertFactory {
Coffee createCoffee();
Dessert createDessert();
IceCream createIceCream(); // 新增方法,所有具体工厂都要修改
}
这就是抽象工厂模式的倾斜特性:增加产品族容易(只需新增一个具体工厂),增加产品等级困难(需要修改抽象工厂及所有具体工厂)。
| 扩展方向 | 难度 | 是否符合开闭原则 |
|---|---|---|
| 新增产品族(新增一个风格的完整搭配) | 容易 | 符合 |
| 新增产品等级(新增一种产品类型) | 困难 | 不符合 |
三、工厂方法 vs 抽象工厂
工厂方法模式和抽象工厂模式同属工厂模式家族,容易混淆,下面从多个维度进行对比。
3.1 本质区别
| 对比维度 | 工厂方法 | 抽象工厂 |
|---|---|---|
| 定义 | 定义一个创建对象的接口,让子类决定实例化哪个类 | 提供一个创建一系列相关对象的接口 |
| 产品数量 | 只生产一种产品 | 生产多种产品(产品族) |
| 抽象工厂 | 只有一个工厂方法 | 有多个工厂方法,每个方法创建一种产品 |
| 具体工厂 | 每个具体工厂只创建一种产品 | 每个具体工厂创建一组产品族 |
| 产品结构 | 一个产品等级结构 | 多个产品等级结构 |
| 新增产品 | 新增一个产品类和对应工厂 | 新增产品族容易,新增产品等级困难 |
3.2 角色对比
工厂方法模式:一个抽象工厂 + 多个具体工厂,每个具体工厂只创建一种产品
实现
实现
创建
创建
抽象工厂
工厂方法 createProduct
具体工厂A
具体工厂B
具体产品A
具体产品B
抽象工厂模式:一个抽象工厂 + 多个具体工厂,每个具体工厂创建一组产品族
实现
实现
创建
创建
创建
创建
抽象工厂
工厂方法1 createProductA
工厂方法2 createProductB
具体工厂1
具体工厂2
产品A1
产品B1
产品A2
产品B2
3.3 关系说明
抽象工厂模式本质上可以看作是工厂方法模式的扩展。当抽象工厂中只有一个工厂方法时,它就退化为工厂方法模式。也就是说:
- 抽象工厂中的每个工厂方法,其实现方式与工厂方法模式是一样的
- 当产品族中只有一个产品等级时,抽象工厂 = 工厂方法
选型建议:
- 如果只需要创建一种产品,使用工厂方法模式
- 如果需要创建一组相关的产品族,且需要保证产品风格一致,使用抽象工厂模式
- 如果产品等级结构稳定,不会频繁新增产品类型,抽象工厂模式非常合适
四、总结
抽象工厂模式的核心思想是提供一个创建一系列相关对象的接口,保证同一工厂创建的产品属于同一产品族,风格一致。
优点:
- 保证产品族一致性:同一工厂创建的产品一定属于同一产品族,客户端不会混搭不同风格的产品
- 符合开闭原则(产品族方向):新增产品族只需新增一个具体工厂,无需修改已有代码
- 客户端与具体产品解耦,客户端只需要知道抽象工厂和抽象产品
- 将一个产品族中的多个产品统一管理,便于维护
缺点:
- 新增产品等级困难:需要在抽象工厂中添加新的工厂方法,导致所有具体工厂都需要修改
- 类的个数增加:每种产品族都需要一个具体工厂类,产品族越多类越多
- 增加了系统的抽象性和理解难度
适用场景:
- 系统需要创建一组相关的产品,且这些产品必须属于同一产品族
- 系统中有多个产品族,客户端需要在不同产品族之间切换
- 产品等级结构相对稳定,不会频繁新增产品类型
- 需要强调一系列相关产品的一致性约束
工厂三兄弟总结:
- 简单工厂:一个工厂通过参数创建多种产品,简单但不遵循开闭原则
- 工厂方法:每种产品对应一个工厂,遵循开闭原则,但只能创建一种产品
- 抽象工厂:每个工厂创建一组产品族,保证产品一致性,新增产品族容易,新增产品等级困难
参考博客:
抽象工厂模式 | 菜鸟教程:https://www.runoob.com/design-pattern/abstract-factory-pattern.html