05. 工厂方法模式
一、工厂方法模式
简单工厂模式虽然简单,但存在一个很严重的问题:当系统中需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,将违背开闭原则。此外,在简单工厂模式中,所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,包含了大量的 if…else… 代码,具体产品与工厂类之间的耦合度高,严重影响了系统的灵活性和扩展性。如何实现增加新产品而不影响已有代码?工厂方法模式为此应运而生。
在 工厂方法模式 中,不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。
工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它在父类中定义一个创建对象的方法,但允许子类决定具体要实例化哪个类。这种模式将类的实例化操作延迟到子类中完成,即由子类来决定究竟应该实例化哪一个类。在工厂方法模式中,创建对象的接口被定义在父类中,而具体的实现则交由子类来完成。这样,客户端只需要知道具体工厂的名称,而无需了解产品的具体创建过程,从而实现了客户端与产品实现之间的解耦。
工厂方法模式的主要角色:
- 抽象工厂(Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特征和功能。
- 具体产品(Concrete Product):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂一一对应。
总的来说,工厂方法模式通过定义一个用于创建对象的接口,让子类决定实例化哪一个类,从而实现了客户端与产品实现之间的解耦,提高了系统的灵活性和可扩展性。
二、C++实现工厂方法模式
将所有产品类公共的代码移至抽象产品类,并在抽象产品类中声明一些抽象方法,以供不同的具体产品类来实现。
// 抽象披萨类
class Pizza
{
private:
std::string name;
public:
Pizza(void);
Pizza(std::string name);
virtual void prepare(void) = 0; // 准备原材料,这里不同披萨准备原材料不同
void bake(void); // 烘烤,这里不同披萨烘烤方式相同
void cut(void); // 切块,这里不同披萨切块方式相同
void box(void); // 装盒,这里不同披萨装盒方式相同
std::string getName();
void setName(std::string name);
};
Pizza::Pizza(void) {}
Pizza::Pizza(std::string name) : name(name) {}
void Pizza::bake(void)
{
std::cout << name + "烘烤" << std::endl;
}
void Pizza::cut(void)
{
std::cout << name + "切块" << std::endl;
}
void Pizza::box(void)
{
std::cout << name + "装盒" << std::endl;
}
std::string Pizza::getName(void)
{
return name;
}
void Pizza::setName(std::string name)
{
this->name = name;
}
在具体产品类中实现了抽象产品类中声明的抽象业务方法,不同的具体产品类可以提供不同的实现。
// 奶酪披萨
class CheesePizza : public Pizza
{
public:
using Pizza::Pizza;
void prepare(void) override;
};
void CheesePizza::prepare(void)
{
std::cout << "准备" + getName() + "的原材料" << std::endl;
std::cout << "准备奶酪" << std::endl;
}
// 水果披萨
class FruitPizza : public Pizza
{
public:
using Pizza::Pizza;
void prepare(void) override;
};
void FruitPizza::prepare(void)
{
std::cout << "准备" + getName() + "的原材料" << std::endl;
std::cout << "准备水果" << std::endl;
}
与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色。抽象工厂可以是接口,也可以是抽象类或者具体类。在抽象工厂中声明了工厂方法但并未实现工厂方法,具体产品对象的创建由其子类负责。客户端针对抽象工厂编程,可在运行时再指定具体工厂类。
// 披萨工厂,根据顾客的订单,来生产不同的披萨
class PizzaFactory
{
public:
virtual Pizza * ceratePizza(void) = 0;
};
具体工厂类实现了工厂方法,不同的具体工厂可以创建不同的具体产品。
// 具体的奶酪披萨工厂
class CheesePizzaFactory : public PizzaFactory
{
public:
Pizza * ceratePizza(void) override;
};
Pizza * CheesePizzaFactory::ceratePizza(void)
{
return new CheesePizza("奶酪披萨");
}
// 具体的水果披萨工厂
class FruitPizzaFactory : public PizzaFactory
{
public:
Pizza * ceratePizza(void) override;
};
Pizza * FruitPizzaFactory::ceratePizza(void)
{
return new FruitPizza("水果披萨");
}
在客户端代码中,只需关心工厂类即可。不同的具体工厂可以创建不同的产品。
// 披萨店,根据顾客的订单,来选择不同的披萨
class PizzaStore
{
private:
PizzaFactory * pizzaFactory;
public:
Pizza * order(void); // 顾客订购披萨
void setPizzaFactory(PizzaFactory * pizzaFactory);
};
void PizzaStore::setPizzaFactory(PizzaFactory * pizzaFactory)
{
this->pizzaFactory = pizzaFactory;
}
Pizza * PizzaStore::order(void)
{
Pizza * pizza = pizzaFactory->ceratePizza(); // 根据顾客的订单,来选择不同的披萨
pizza->prepare();
pizza->bake();
pizza->cut();
pizza->box();
return pizza;
}
main() 函数:
#include <iostream>
int main(void)
{
PizzaStore pizzaStore;
// 顾客订购奶酪披萨
PizzaFactory * pizzaCheessFactory = new CheesePizzaFactory();
pizzaStore.setPizzaFactory(pizzaCheessFactory);
Pizza * cheesePizza = pizzaStore.order();
std::cout << std::endl;
// 顾客订购水果披萨
FruitPizzaFactory * pizzaFruitFactory = new FruitPizzaFactory();
pizzaStore.setPizzaFactory(pizzaFruitFactory);
Pizza * fruitPizza = pizzaStore.order();
delete pizzaCheessFactory;
delete pizzaFruitFactory;
delete cheesePizza;
delete fruitPizza;
return 0;
}
三、工厂方法的模式总结
3.1、工厂方法模式的优点
- 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节。用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,正是因为所有的具体工厂类都具有同一抽象父类。
- 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合开闭原则。
3.2、工厂方法模式的缺点
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到 DOM、反射等技术,增加了系统的实现难度。
3.3、工厂方法模式的适用场景
- 客户端不知道其所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
- 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。