大话设计模式 —— 第八章《工厂方法模式》C++ 代码实现
目录
前言
在本书的第一章的简单工厂模式,发现简单工厂模式存在一系列问题:
- 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响;
- 违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,那样就需要工厂类中做判断(比如:
if
),根据不同的条件或者前提创建不同的对象。这也就造成了,当增加新的产品时,需要修改工厂类,也就是其增加其判断分支。- 简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。
为了解决上述的问题,可以又使用了一种新的设计模式:工厂方法模式。
工厂方法模式定义
- 工厂方法模式,又称工厂模式、多态工厂模式和虚拟构造器模式,通过定义一个抽象工厂类来负责定义公共的接口,而其子类负责到底实例化哪个类,那么该类的实例化将(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化(创建)哪一个类。
- 工厂方法模式之所以可以解决简单工厂的问题,是因为工厂方法模式把具体产品的创建推迟到工厂类的子类(具体工厂)中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。这样工厂方法模式在添加新产品的时候就不修改工厂类逻辑而是添加新的工厂子类,符合开放封闭原则,克服了简单工厂模式中缺点。
- 抽象产品(Product): 抽象类或接口,是 具体产品的父类 描述具体产品的公共接口
- 具体产品(Concrete Product): 抽象产品的子类;具体工厂类创建的目标类 描述生产的具体产品,需要实现Product接口。
- 抽象工厂(Creator): 一个接口或抽象类,是具体工厂的父类 描述具体工厂的公共方法。该方法返回一个Product类型的对象。
- 具体工厂(Concrete Creator):抽象工厂的子类;被外界调用 描述具体工厂;具体工厂重写抽象工厂中的对应方法使该方法返回ConcreteProduct的实例。
使用步骤:
- 步骤1: 创建抽象工厂类,定义具体工厂的公共接口;
- 步骤2: 创建抽象产品类 ,定义具体产品的公共接口;
- 步骤3: 创建具体产品类(继承抽象产品类)和 定义生产的具体产品;
- 步骤4:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
- 步骤5:外界通过调用具体工厂类的方法,从而创建不同 ConcreteProduct 类的实例
下面用C++ 代码实现大话设计模式的本章代码:
#include<iostream>
#include<string>
using namespace std;
class LeiFeng // 创建抽象产品类 ,定义具体产品的公共接口;
{
public:
virtual ~LeiFeng() = default;
virtual void Sweep() = 0;
virtual void Wash() = 0;
virtual void BuyRice() = 0;
};
// 创建具体产品类(继承抽象产品类), 定义生产的具体产品;
class Undergraduate final :public LeiFeng
{
public:
void Sweep()override
{
cout << "大学生-扫地" << endl;
}
void Wash()override
{
cout << "大学生-洗衣" << endl;
}
void BuyRice()override
{
cout << "大学生-买米" << endl;
}
};
class Volunteer final :public LeiFeng
{// 创建具体产品类(继承抽象产品类), 定义生产的具体产品;
public:
void Sweep()override
{
cout << "志愿者-扫地" << endl;
}
void Wash()override
{
cout << "志愿者-洗衣" << endl;
}
void BuyRice()override
{
cout << "志愿者-买米" << endl;
}
};
// 创建具体产品类(继承抽象产品类), 定义生产的具体产品;
class AbstractFactory
{
public:
virtual ~AbstractFactory() = default;
virtual LeiFeng *CreateLeifeng() = 0;
};
//创建具体工厂类(继承抽象工厂类),定义创建对应的 ConcreteProduct实例的方法;
class UndergraduateFactory final :public AbstractFactory
{
public:
LeiFeng* CreateLeifeng()override
{
return new Undergraduate;
}
};
class VolunteerFactory final :public AbstractFactory
{
public:
LeiFeng* CreateLeifeng()override
{
return new Volunteer;
}
};
int main()
{
AbstractFactory* af = new VolunteerFactory;
// 外界通过调用具体工厂类的方法,从而创建不同 ConcreteProduct 类的实例
LeiFeng* lf = af->CreateLeifeng(); // 调用的是 VolunteerFactory的 CreateLeifeng函数
lf->BuyRice();
lf->Sweep();
lf->Wash();
if (af != nullptr)
{
delete af;
af = nullptr;
}
if (lf != nullptr)
{
delete lf;
lf = nullptr;
}
system("pause");
return 0;
}
运行后截图为:
优点
- 更符合开-闭原则 —— 比如当新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可 。但是简单工厂模式需要修改工厂类的判断逻辑
- 符合单一职责原则 —— 每个具体工厂类只负责创建对应的产品。但是简单工厂中的工厂类存在复杂的switch 或 if 逻辑判断
- 不使用静态工厂方法,可以形成基于继承的等级结构。但是简单工厂模式的工厂类使用静态工厂方法
- 典型的解耦框架。高层模块只需要知道产品的抽象类,其他的实现类都不需要关心,符合迪米特法则,符合依赖倒置原则,符合里氏替换原则。
- 还可以使代码结构清晰,有效地封装变化。在编程中,产品类的实例化有时候是比较复杂和多变的,通过工厂模式,将产品的实例化封装起来,使得调用者根本无需关心产品的实例化过程,只需依赖工厂即可得到自己想要的产品。
- 对调用者屏蔽具体的产品类。如果使用工厂模式,调用者只关心产品的接口就可以了,至于具体的实现,调用者根本无需关心。即使变更了具体的实现,对调用者来说没有任何影响。
- 降低耦合度。产品类的实例化通常来说是很复杂的,它需要依赖很多的类,而这些类对于调用者来说根本无需知道,如果使用了工厂方法,我们需要做的仅仅是实例化好产品类,然后交给调用者使用。对调用者来说,产品所依赖的类都是透明的。
总结:工厂模式可以说是简单工厂模式的进一步抽象和拓展,在保留了简单工厂的封装优点的同时,让扩展变得简单,让继承变得可行,增加了多态性的体现。
缺点
- 当添加新产品时,除了增加新的具体产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;同时,有更多的类需要编译和运行,会给系统带来一些额外的开销;也增加了额外的开发量
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
- 虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类;
- 一个具体工厂只能创建一种具体产品
适合情景
- 首先,作为一种创建型模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过new就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
- 其次,工厂模式是一种典型的解耦模式,迪米特法则在工厂模式中表现的尤为明显。假如调用者自己组装产品需要增加依赖关系时,可以考虑使用工厂模式。将会大大降低对象之间的耦合度。
- 再次,由于工厂模式是依靠抽象架构的,它把实例化产品的任务交由实现类完成,扩展性比较好。也就是说,当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。
- 当一个类不知道它所必须创建的对象的类的时候。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可;
- 当一个类希望由它的子类来指定它所创建的对象的时候。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。