定义
抽象工厂是一种创建型设计模式,它能创建一系列相关的对象,而无需指定其具体类。
前言
1. 问题
假设你正在开发一款家具商店模拟器。你的代码中包括一些类,用于表示:
- 一系列相关产品,例如椅子(Chair)、沙发(Sofa)和咖啡桌(CoffeeTable)
- 系列产品的不同变体,例如你可以使用现代(Modern)、维多利亚(Victorian)和装饰风艺术(ArtDeco)等风格生成这些产品
你需要设法单独生成每件家具对象,这样才能确保其风格一致。如果顾客收到的家具风格不一样,他们可不会开心。
此外, 你也不希望在添加新产品或新风格时修改已有代码。家具供应商对于产品目录的更新非常频繁,你不会想在每次更新时都去修改核心代码的。
2. 解决方案
首先,抽象工厂模式建议为系列中的每件产品明确声明接口(例如椅子、沙发或咖啡桌)。然后,确保所有产品变体都继承这些接口。例如,所有风格的椅子都实现椅子接口;所有风格的咖啡桌都实现咖啡桌接口,以此类推。
接下来, 我们需要声明抽象工厂——包含系列中所有产品构造方法的接口。例如创建椅子(createChair)、创建沙发(createSofa)和 创建咖啡桌(createCoffeeTable)。这些方法必须返回抽象产品类型,即我们之前抽取的那些接口: 椅子,沙发和咖啡桌等等。
那么该如何处理产品变体呢? 对于系列产品的每个变体, 我们都将基于抽象工厂接口创建不同的工厂类。每个工厂类都只能返回特定类别的产品,例如, 现代家具工厂(ModernFurnitureFactory)只能创建现代椅(ModernChair)、现代沙发(ModernSofa)和现代咖啡桌(ModernCoffeeTable)对象。
客户端代码可以通过相应的抽象接口调用工厂和产品类。你无需修改实际客户端代码,就能更改传递给客户端的工厂类,也能更改客户端代码接收的产品变体。
假设客户端想要工厂创建一把椅子。客户端无需了解工厂类,也不用管工厂类创建出的椅子类型。无论是现代风格,还是维多利亚风格的椅子,对于客户端来说没有分别,它只需调用抽象椅子接口就可以了。这样一来,客户端只需知道椅子以某种方式实现了坐下(sitOn)方法就足够了。此外,无论工厂返回的是何种椅子变体,它都会和由同一工厂对象创建的沙发或咖啡桌风格一致。
最后一点说明:如果客户端仅接触抽象接口,那么谁来创建实际的工厂对象呢?一般情况下,应用程序会在初始化阶段创建具体工厂对象。而在此之前,应用程序必须根据配置文件或环境设定选择工厂类别。
结构
- 抽象产品(Abstract Product)为构成系列产品的一组不同但相关的产品声明接口。
- 具体产品(Concrete Product)是抽象产品的多种不同类型实现。所有变体(维多利亚/现代)都必须实现相应的抽象产品(椅子/沙发)。
- 抽象工厂(Abstract Factory)接口声明了一组创建各种抽象产品的方法。
- 具体工厂(Concrete Factory)实现抽象工厂的构建方法。每个具体工厂都对应特定产品变体,且仅创建此种产品变体。
- 尽管具体工厂会对具体产品进行初始化,其构建方法签名必须返回相应的抽象产品。这样,使用工厂类的客户端代码就不会与工厂创建的特定产品变体耦合。客户端(Client)只需通过抽象接口调用工厂和产品对象,就能与任何具体工厂/产品变体交互。
适用场景
- 如果代码需要与多个不同系列的相关产品交互,但是由于无法提前获取相关信息,或者出于对未来扩展性的考虑,你不希望代码基于产品的具体类进行构建,在这种情况下,你可以使用抽象工厂。
抽象工厂为你提供了一个接口,可用于创建每个系列产品的对象。只要代码通过该接口创建对象,那么你就不会生成与应用程序已生成的产品类型不一致的产品。
- 如果你有一个基于一组抽象方法的类,且其主要功能因此变得不明确,那么在这种情况下可以考虑使用抽象工厂模式。
在设计良好的程序中,每个类仅负责一件事。如果一个类与多种类型产品交互,就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。
实现方式
- 以不同的产品类型与产品变体为维度绘制矩阵。
- 为所有产品声明抽象产品接口。然后让所有具体产品类实现这些接口。
- 声明抽象工厂接口,并且在接口中为所有抽象产品提供一组构建方法。
- 为每种产品变体实现一个具体工厂类。
- 在应用程序中开发初始化代码。该代码根据应用程序配置或当前环境,对特定具体工厂类进行初始化。然后将该工厂对象传递给所有需要创建产品的类。
- 找出代码中所有对产品构造函数的直接调用,将其替换为对工厂对象中相应构建方法的调用。
优点
- 你可以确保同一工厂生成的产品相互匹配。
- 你可以避免客户端和具体产品代码的耦合。
- 单一职责原则。你可以将产品生成代码抽取到同一位置,使得代码易于维护。
- 开闭原则。向应用程序中引入新产品变体时,你无需修改客户端代码。
缺点
由于采用该模式需要向应用中引入众多接口和类,代码可能会比之前更加复杂。
实例
AbstractFactory.hpp
#ifndef A2AC74A4_E96A_4B51_937C_1E888FCCF4EC
#define A2AC74A4_E96A_4B51_937C_1E888FCCF4EC
#include <memory>
#include "AbstractProduct.hpp"
class Factory{
public:
virtual shared_ptr<Movie> productMovie()=0;
virtual shared_ptr<Book> productBook() = 0;
};
#endif /* A2AC74A4_E96A_4B51_937C_1E888FCCF4EC */
ConcreteFactory.hpp
#ifndef B6D4A87E_8B55_4433_9446_CD0A4E05F3EB
#define B6D4A87E_8B55_4433_9446_CD0A4E05F3EB
#include "AbstractFactory.hpp"
#include "AbstractProduct.hpp"
#include "ConcreteProduct.hpp"
#include <memory>
using namespace std;
class ChineseProduct: public Factory{
public:
shared_ptr<Movie> productMovie() override{
return make_shared<ChineseMovie>();
}
shared_ptr<Book> productBook() override{
return make_shared<ChineseBook>();
}
};
class JapaneseProduct : public Factory{
public:
shared_ptr<Movie> productMovie()override{
return make_shared<JapaneseMovie>();
}
shared_ptr<Book> productBook() override{
return make_shared<JapaneseBook>();
}
};
#endif /* B6D4A87E_8B55_4433_9446_CD0A4E05F3EB */
AbstractProduct.hpp
#ifndef B7A6F158_17F4_48CB_864F_79A6515F2190
#define B7A6F158_17F4_48CB_864F_79A6515F2190
#include <string>
using namespace std;
class Movie{
public:
virtual string showMoiewName() = 0;
};
class Book{
public :
virtual string showBookName() = 0;
};
#endif /* B7A6F158_17F4_48CB_864F_79A6515F2190 */
ConcreteProduct.hpp
#ifndef C0BF2BA5_C17A_4007_B403_C5CCB379961C
#define C0BF2BA5_C17A_4007_B403_C5CCB379961C
#include <string>
#include <iostream>
#include "AbstractProduct.hpp"
class ChineseMovie : public Movie {
string showMoiewName() override {
return "让子弹飞";
}
};
class JapaneseMovie : public Movie{
string showMoiewName() override {
return "千与千寻";
}
};
class ChineseBook : public Book{
string showBookName override {
return "三国演义";
}
};
class JapaneseBook : public Book{
string showBookName() override {
return "白夜行";
}
};
#endif /* C0BF2BA5_C17A_4007_B403_C5CCB379961C */
main.cpp
#include "AbstractFactory.hpp"
#include "AbstractProduct.hpp"
#include "ConcreteFactory.hpp"
#include "ConcreteProduct.hpp"
#include "iostream"
using namespace std;
int main(int argc, char const *argv[])
{
shared_ptr<Factory> factory;
string conf = "china";
if(conf == "china"){
factory = make_shared<ChineseProduct>();
}else if(conf == "japan"){
factory = make_shared<JapaneseProduct>();
}else{
cout << " ERROR: Unknown" << endl;
}
shared_ptr<Movie> movie;
shared_ptr<Book> book;
movie = factory->productMovie();
book = factory->productBook();
std::cout << "获取一部电影: " << movie->showMoiewName() << std::endl;
std::cout << "获取一本书: " << book->showBookName() << std::endl;
return 0;
}