三种工厂模式-创建对象的灵活工具
在 23 种设计模式中,总体来说设计模式分为三大类:
- 创建型模式
- 结构型模式
- 行为型模式
其中,创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。本文将介绍工厂方法模式、抽象工厂模式 2 种,其中,工厂方法模式一般可以分为简单工厂模式及工厂模式。
简单工厂模式
概念
简单工厂模式是一种创建型设计模式,旨在引入一个工厂类来创建不同类型的对象,无需直接实例化这些对象。它提供了一种灵活的方式来管理对象的创建,使代码更易于维护和扩展。
简单工厂模式又叫静态方法模式(因为工厂类定义了一个静态方法)。在现实生活中,工厂负责生产产品;同样在设计模式中,简单工厂模式可以理解为负责生产对象的一个类,称为“工厂类”。
解决的问题
简单工厂模式将“类实例化的操作”与“使用对象的操作”分开,让使用者不用知道具体参数就可以实例化出所需要的“产品”类,从而避免了在客户端代码中显式指定,实现了解耦。使用者可以直接消费产品而不需要知道其生产的细节。
模式原理
组成(角色):
-
抽象产品(Product):具体产品的父类,描述产品的公共接口。
-
具体产品(Concrete Product):抽象产品的子类,描述生产的具体产品。
-
工厂(Creator):被外界调用,根据传入不同参数从而创建不同具体产品类的实例。
使用步骤
a. 创建抽象产品类,定义具体产品的公共接口。
b. 创建具体产品类(继承抽象产品类),定义生产的具体产品。
c. 创建工厂类,通过创建静态方法根据传入不同参数从而创建不同具体产品类的实例。
d. 外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例。
案例
当前我有三个产品,华为汽车(HuaweiCar)、小米汽车(XiaomiCar)、特斯拉汽车(TeslaCar),该三种产品都有共同的属性电池,共有方法前进、后退; 于是我们决定将该三种产品,抽象出一个叫做汽车(Car)的类,代码如下:
// 车辆接口定义了车辆应该支持的基本操作
class Car {
public:
// 虚析构函数,确保派生类的正确清理
virtual ~Car() {}
// 前进
virtual void Forward() = 0;
// 后退
virtual void Backward() = 0;
protected:
int m_nBattery; // 电池
};
然后分别定义三个产品类:
// 华为类,实现了车辆接口。
class HuaweiCar : public Car {
public:
HuaweiCar();
virtual void Forward() override;
virtual void Backward() override;
};
// 小米类,实现了车辆接口
class XiaomiCar : public Car {
public:
XiaomiCar();
virtual void Forward() override;
virtual void Backward() override;
};
// 特斯拉类,实现了车辆接口
class TeslaCar : public Car {
public:
TeslaCar();
virtual void Forward() override;
virtual void Backward() override;
};
然后定义工厂类,该类的作用是生产产品(new 处所需的对象),如下:
// 简单工厂模式
class SimpleCarFactory {
public:
static Car* createCar(CarType nType_)
{
switch (nType_)
{
case CarType::HUAWEI:
return new HuaweiCar();
case CarType::XIAOMI:
return new XiaomiCar();
case CarType::TESLA:
return new TeslaCar();
default:
throw std::invalid_argument("Unknown car type");
}
}
};
至此,就完成了简单工厂模式,只需要在需要对象的地方,调用工厂,传入想要的对象类型,即可得到对象。但是,简单工厂模式有如下缺陷:
- 违反开放封闭原则:简单工厂模式在增加新产品时需要修改工厂类的代码。这违反了“开放封闭原则”,即应该对扩展开放,对修改封闭。
- 工厂类职责过重:在简单工厂中,工厂类负责创建所有产品的实例。随着产品类型的增加,工厂类的职责会变得越来越重,不易维护。
- 不易扩展:如果要添加新的产品类型,必须修改工厂类的代码。这不仅违反了开放封闭原则,还可能影响现有代码的稳定性。
例如,若在增加理想汽车类,那么工厂类的代码就必须更改如下:
// 简单工厂模式
class SimpleCarFactory {
public:
static Car* createCar(CarType nType_)
{
switch (nType_)
{
case CarType::HUAWEI:
return new HuaweiCar();
case CarType::XIAOMI:
return new XiaomiCar();
case CarType::TESLA:
return new TeslaCar();
case CarType::LiXIANG:
return new LiXiangCar(); // 新增代码
default:
throw std::invalid_argument("Unknown car type");
}
}
};
工厂方法模式
工厂方法模式允许我们在不直接实例化对象的情况下,通过使用工厂来创建不同类型的产品对象。相对于简单工厂模式,这种方式使得添加新产品类时,只需添加相应的工厂类即可,无需修改现有代码,符合开闭原则。
模式原理
组成(角色):
- 抽象产品(Product):定义了产品的通用接口,具体产品类必须实现这个接口。
- 具体产品(Concrete Product):实现了产品接口,代表不同类型的产品。
- 抽象工厂(Factory):定义了创建产品的接口,具体工厂类必须实现这个接口。
- 具体工厂类(ConcreteFactory):实现了抽象工厂接口,用于创建具体产品的实例。
相对于简单工厂模式,工厂方法模式其实就是每个产品类都有对应的工厂,而这些具体的工厂类都是基于同一个抽象工厂类,每个产品都拥有自己对应的工厂,那么,新增产品类时,只需要新增对应的工厂类即可。
同样是简单工厂模式中的例子,如今存在 3 款产品,为每一种产品都设计一个工厂,而工厂目前只有一个任务,能够生产产品(车),于是我们定义工厂类:
// 工厂方法模式
class CarFactory
{
public:
virtual std::unique_ptr<Car> createCar() = 0;
};
上述的工厂只是定义了创建产品的接口,而实现则由各自产品对应的工厂去实现:
// 具体工厂类:华为汽车工厂
class HuaweiCarFactory : public CarFactory {
public:
std::unique_ptr<Car> createCar() override {
return std::make_unique<HuaweiCar>();
}
};
// 具体工厂类:小米汽车工厂
class XiaomiCarFactory : public CarFactory {
public:
std::unique_ptr<Car> createCar() override {
return std::make_unique<XiaomiCar>();
}
};
// 具体工厂类:特斯拉汽车工厂
class TeslaCarFactory : public CarFactory {
public:
std::unique_ptr<Car> createCar() override {
return std::make_unique<TeslaCar>();
}
};
上述代码即工厂方法模式的核心,主要思想即:各自产品拥有各自的工厂!这种模式的优点在于,当增加新的产品时,仅需要新增产品类及对应产品的工厂,无需修改原有的代码,即对扩展开放,对修改关闭。
抽象工厂模式
模式原理
- 抽象工厂(Abstract Factory):定义了一组创建相关产品的接口。每个具体工厂类都实现这个接口,负责创建一系列相关的产品。
- 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建一组相关的产品。
- 抽象产品(Abstract Product):定义了一组产品对象的共同接口或抽象类,描述了产品对象的公共方法。
- 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。
抽象工厂模式通常涉及一族相关的产品,每个具体工厂类负责创建该族中的具体产品。客户端通过使用抽象工厂接口来创建产品对象,而不需要直接使用具体产品的实现类。
抛开上述描述不谈,最简单的理解方式就是:产品不再单一,工厂职责也不再单一。这里引入品牌和产品的概念,其实也就是产品族和产品等级的概念。
比如,将上述的例子扩展,华为不仅有车这一个产品,还有手机这一个产品,华为就是品牌,车和手机就是产品,小米同理。此时,按照工厂方法模式,我们可以这样做:
- 抽象一个手机产品类
- 实现各个品牌的手机产品
- 抽象一个手机工厂类
- 实现各个品牌的手机工厂
这样做当然可以,可以看作华为的车工厂和手机工厂完全是两个独立的东西,没有任何联系。抽象工厂模式可以理解为:合并 2 个相关的工厂,让该工厂既能够生产手机、又能够生产车。看如下代码:
// 定义手机的基本操作
class Phone {
public:
Phone();
// 虚析构函数,确保派生类的正确清理
virtual ~Phone() {}
// call
virtual void Call() = 0;
};
// 华为手机
class HuaweiPhone : public Phone
{
public:
HuaweiPhone();
virtual void Call() override;
};
// 华为手机
class XiaomiPhone : public Phone
{
public:
XiaomiPhone();
virtual void Call() override;
};
工厂如下定义:
// 抽象工厂模式
class Factory
{
public:
virtual std::unique_ptr<Car> createCar() = 0; // 生产车的接口
virtual std::unique_ptr<Phone> createPhone() = 0; // 生产手机的接口
};
// 具体工厂类:华为工厂
class HuaweiFactory : public Factory {
public:
std::unique_ptr<Car> createCar() override {
return std::make_unique<HuaweiCar>();
}
std::unique_ptr<Phone> createPhone() override {
return std::make_unique<HuaweiPhone>();
}
};
// 具体工厂类:小米工厂
class XiaomiFactory : public Factory {
public:
std::unique_ptr<Car> createCar() override {
return std::make_unique<XiaomiCar>();
}
std::unique_ptr<Phone> createPhone() override {
return std::make_unique<XiaomiPhone>();
}
};
抽象工厂的优缺点
优点:
- 产品族一致性:抽象工厂模式确保由同一个工厂创建的产品之间具有一致性。这对于创建一组相关的产品非常有用,例如不同操作系统下的窗口、按钮和文本框。
- 开放-封闭原则:抽象工厂模式允许在不修改现有代码的情况下添加新的产品族。只需创建新的具体工厂类,而不必更改客户端代码。
- 隐藏具体实现:客户端只与抽象工厂和抽象产品接口交互,不需要知道具体产品的实现细节。这有助于降低耦合度。
缺点:
- 扩展困难:如果需要添加新的产品,除了创建新的具体产品类外,还必须修改抽象工厂接口和所有具体工厂类。这可能导致代码变得复杂且难以维护。
- 产品族数量限制:抽象工厂模式适用于一组相关的产品,但如果产品族数量不断增加,会导致类的爆炸性增长。
以上完整代码请见 GitHub 仓库。