设计模式:工厂方法模式(Factory Method)
设计模式:工厂方法模式(Factory Method)
工厂方法模式(Factory Method)属于创建型模式(Creational Pattern)的一种。
创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。
创建型模式在创建什么(What),由谁创建(Who),何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。
模式动机
简单工厂模式(Simple Factory)只有一个简单工厂类 SimpleFactory 负责所有产品的创建,现在对系统进行修改。
我们先定义一个抽象的工厂类,再定义具体的工厂类,它们实现在抽象工厂类中定义的方法。这种抽象化的结果使这种结构可以在不修改具体工厂类的情况下引进新的产品,如果出现新的产品类型,只需要为这种新类型的产品创建一个具体的工厂类就可以获得该新产品的实例,这一特点无疑使得工厂方法模式(Factory Method)具有超越简单工厂模式(Simple Factory)的优越性,更加符合“开闭原则”。
模式定义
工厂方法模式(Factory Method)也叫虚拟构造器模式(Virtual Constructor)或者多态工厂模式(Polymorphic Factory)。
在工厂方法模式(Factory Method)中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
模式结构
工厂方法模式包含如下角色:
- 抽象工厂(Abstract Factory):声明了一组用于创建产品对象的方法,每个方法对应一种产品类型。抽象工厂可以是接口或抽象类。
- 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体产品对象的实例。
- 抽象产品(Abstract Product):定义了一组产品对象的共同接口或抽象类,描述了产品对象的公共方法。
- 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。
抽象工厂模式通常涉及一族相关的产品,每个具体工厂类负责创建该族中的具体产品。客户端通过使用抽象工厂接口来创建产品对象,而不需要直接使用具体产品的实现类。
时序图
模式实现
抽象工厂类 AbstractFactory.h:
#ifndef _ABSTRCAT_FACTORY_H_
#define _ABSTRCAT_FACTORY_H_
#include <string>
#include "AbstractProduct.h"
class AbstractFactory
{
public:
virtual AbstractProduct* factoryMethod() = 0;
};
#endif // !_ABSTRCAT_FACTORY_H_
具体工厂 A 类:
#ifndef _CONCRETE_FACTORY_A_H_
#define _CONCRETE_FACTORY_A_H_
#include "AbstractFactory.h"
#include "ConcreteProductA.h"
class ConcreteFactoryA : public AbstractFactory
{
public:
AbstractProduct* factoryMethod() override
{
return new ConcreteProductA();
}
};
#endif // !_CONCRETE_FACTORY_A_H_
具体工厂 B 类:
#ifndef _CONCRETE_FACTORY_B_H_
#define _CONCRETE_FACTORY_B_H_
#include "AbstractFactory.h"
#include "ConcreteProductB.h"
class ConcreteFactoryB : public AbstractFactory
{
public:
AbstractProduct* factoryMethod() override
{
return new ConcreteProductB();
}
};
#endif // !_CONCRETE_FACTORY_B_H_
抽象产品类 AbstractProduct.h:
#ifndef _ABSTRACT_PRODUCT_H_
#define _ABSTRACT_PRODUCT_H_
#include <iostream>
class AbstractProduct
{
public:
virtual void use() = 0;
};
#endif // !_ABSTRACT_PRODUCT_H_
具体产品 A 类:
#ifndef _CONCRETE_PRODUCT_A_H_
#define _CONCRETE_PRODUCT_A_H_
#include "AbstractProduct.h"
class ConcreteProductA : public AbstractProduct
{
public:
void use() override
{
std::cout << "Using Concrete Product A" << std::endl;
}
};
#endif // !_CONCRETE_PRODUCT_A_H_
具体产品 B 类:
#ifndef _CONCRETE_PRODUCT_B_H_
#define _CONCRETE_PRODUCT_B_H_
#include "AbstractProduct.h"
class ConcreteProductB : public AbstractProduct
{
public:
void use() override
{
std::cout << "Using Concrete Product B" << std::endl;
}
};
#endif // !_CONCRETE_PRODUCT_B_H_
测试
测试代码 main.cpp:
#include <stdlib.h>
#include "ConcreteFactoryA.h"
#include "ConcreteFactoryB.h"
#include "ConcreteProductA.h"
#include "ConcreteProductB.h"
int main()
{
AbstractFactory* pfa = new ConcreteFactoryA();
AbstractProduct* productA = pfa->factoryMethod();
AbstractFactory* pfb = new ConcreteFactoryB();
AbstractProduct* productB = pfb->factoryMethod();
if (productA)
productA->use();
if (productB)
productB->use();
system("pause");
return 0;
}
运行结果:
模式分析
工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。
在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。
实例:日志记录器
某系统日志记录器要求支持多种日志记录方式,如文件记录、数据库记录等,且用户可以根据要求动态选择日志记录方式, 现使用工厂方法模式设计该系统。
结构图:
时序图:
优缺点
优点:
- 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
- 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
缺点:
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
适用环境
在以下情况下可以使用工厂方法模式:
- 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
- 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
模式拓展
- 使用多个工厂方法:在抽象工厂角色中可以定义多个工厂方法,从而使具体工厂角色实现这些不同的工厂方法,这些方法可以包含不同的业务逻辑,以满足对不同的产品对象的需求。
- 产品对象的重复使用:工厂对象将已经创建过的产品保存到一个集合(如数组、List等)中,然后根据客户对产品的请求,对集合进行查询。如果有满足要求的产品对象,就直接将该产品返回客户端;如果集合中没有这样的产品对象,那么就创建一个新的满足要求的产品对象,然后将这个对象在增加到集合中,再返回给客户端。
- 多态性的丧失和模式的退化:如果工厂仅仅返回一个具体产品对象,便违背了工厂方法的用意,发生退化,此时就不再是工厂方法模式了。一般来说,工厂对象应当有一个抽象的父类型,如果工厂等级结构中只有一个具体工厂类的话,抽象工厂就可以省略,也将发生了退化。当只有一个具体工厂,在具体工厂中可以创建所有的产品对象,并且工厂方法设计为静态方法时,工厂方法模式就退化成简单工厂模式。
工厂方法模式与简单工厂模式的区别
不同于简单工厂模式只定义一个工厂方法来创建产品,工厂方法模式的每个产品都有一个对应的工厂类来创建。这样每个产品类和工厂类相互独立,可以独立进行扩展,符合开放-封闭原则。
如果使用工厂方法模式,每个产品类型都有一个对应的工厂类来创建,工厂方法可以独立进行扩展,每个工厂类只需要关心自己负责的产品类型,代码更加清晰和易于维护。客户端代码只需要知道所需的产品类型,选择对应的工厂类即可,不需要知道具体的创建细节。