工厂方法模式 - STEMHA's Blog

工厂方法模式

如何赋予派生类创建适当对象的责任。(how derived classes can be given the responsibility of creating appropriate objects.)

基本概念

工厂方法模式(Factory Method Pattern)

  • 工厂模式是创建型模式的一种。
  • 工厂生产商品,软件工厂则生产对象。

应用场景:

  • 工厂方法模式在计算机的很多领域得到应用,它的应用遍布各种工具箱(toolkit)和框架(framework)。 当我们不能实现知道类是什么样子和我们需要实例化哪些子类对象时候,就可以使用工厂方法模式。

比如,C++常常采用下面的方式创建对象:

1
2
SomeClass someClassObject_1 = SomeClass(); //栈中分配 
SomeClass * someClassObject_2 = new SomeClass(); //堆中分配

上述方法的问题在于,使用SomeClass的对象的代码现在突然变得依赖于SomeClass的具体实现。使用new创建对象没有什么错,但是它带有将我们的创建对象的代码和具体实现的代码紧密耦合起来了。
这违反了原则“对接口而不是针对实现编程”。(code to an interface and not to an implementation.)这句话也就是说尽量在有需求来的时候,我们类中实现的代码尽量不要变化,而是通过各种接口的组合来解决新需求。

举个例子,如果创建对象需要一系列复杂的初始化操作,比如需要关联其他成员,查配置文件,查数据库表,这时候该怎么办?
如果都写到构造函数里面,那构造函数就会很长很长,代码可读性也不好。
那么比较好的方式是什么呢?计算机里面解决问题的方法就是加一个层。我们就加一个专门的层——工厂类,来专门负责对象的创建工作。

形式上,工厂方法被定义为以下形式:

  • 提供一个用于对象创建的接口,但将对象的实例化委托给子类。

类图 Class Diagram

The class diagram consists of the following entities:

  • Product:抽象产品角色。
  • Concrete Product:具体产品角色,工厂方法模式所创建的任何对象都是这个角色的实例。
  • Creator:工厂类角色,工厂方法模式的核心,含有与应用紧密相关的商业逻辑。工厂类在客户端的直接调用下创建产品对象。
  • Concrete Creator:具体工厂类角色。

实例

让我们继续制造飞机的过程吧!假设我们正在尝试为F-16战斗机建模。client代码需要为喷气式战斗机构造引擎并进行试飞。
该类的简单实现将如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class F16 {
F16Engine engine; //引擎
F16Cockpit cockpit; //驾驶舱
protected:
void makeF16() //造飞机
{
engine = F16Engine(); //造引擎
cockpit = F16Cockpit(); //造驾驶舱
}
public:
void fly() //试飞
{
makeF16();
cout<<"F16 with bad design flying";
}
}

class Client {
public:
void Clientmain()
{
// We instantiate from a concrete class, thus tying
// ourselves to it
F16 f16 = F16();
f16.fly();
}
}

在上面的代码中,我们建造飞机时候使用F16类的具体实现。这时候突然飞机的版本要更新了,我们也要建造它,肯定需要在程序中对新版本的飞机进行表达,这时候该怎么办呢?

  • 如果采用上面的程序的话,我们只能在新建F16实例的地方更换client代码,比如之前创建的是F16,我们后来想要创建F16B,就得在client代码里面改。
  • 另一种解决方法是将对象的创建封装在另一个对象中,该对象仅负责更新和新建所要求的F-16变体,到时候想。

我们来说说第二种方法。首先,假设我们要表示F16的A变体和B变体,那么代码看起来像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class F16SimpleFactory {
public:
F16 makeF16(string variant)
{
switch (variant) {
case "A":
return F16A();
case "B":
return F16B();
default:
return F16();
}
}
}

上面是简单工厂模式的示例,但是上面这种代码还不是模式,而应该算作常见的编程习惯。

您也可以将make方法标记为static,以跳过工厂对象创建步骤(意思是直接通过类名加静态方法来进行调用,而不是在对象中进行封装)。 但是,由于静态方法不能在子类中覆盖,因为它们是类的唯一方法,因此我们将不能对静态工厂进行子类化。

但是,如果我们希望将F16对象部分的创建保持在同一类中,并且仍然能够引入新的F16变体,则可以对F16进行子类化,并将正确的F16变体对象的创建委托给子类处理。 那么这个新方法正是工厂方法模式! 这里的方法是makeF16(),我们将使其表现得像产生适当F16变体的工厂。 让我们继续前进,我们引入了两个这样的子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class F16 {
IEngine engine;
ICockpit cockpit;

protected:
virtual F16* makeF16() //这里是制造部分
{
engine = F16Engine();
cockpit = F16Cockpit();
return this;
}

public:
void taxi()
{
cout<<"F16 is taxing on the runway !"<<endl;
}

void fly()
{
// Note here carefully, the superclass F16 doesn't know
// what type of F-16 variant it was returned.
F16 f16 = makeF16();
f16.taxi();
cout<<"F16 is in the air !"<<endl;
}
}

class F16A:public F16 {
public:
virtual F16* makeF16() override //这里是制造部分
{
super.makeF16();
engine = F16AEngine();
return this;
}
}

class F16B:public F16 override
{
public:
virtual F16* makeF16()
{
super.makeF16();
engine = F16BEngine();
return this;
}
}

看上面的代码,我们使用子类继承,然后在子类中实现特殊化的引擎对象。 工厂方法可以提供也可以不提供默认或通用的实现,但是可以通过覆盖create / make方法来使子类专门化或修改产品。 在我们的示例中,变体模型仅具有不同的发动机,但座舱相同。 客户端代码现在可以使用更新的模型,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Client {
public void main()
{
vector<F16 *> myAirForce;
F16 * f16A =new F16A();
F16 * f16B =new F16B();
myAirForce.push_back(f16A);
myAirForce.push_back(f16B);

for (F16 *f16 : myAirForce) {
f16->fly();
}
}
}

请注意,工厂模式是借助抽象类和多态来实现的, 在我们的情况下,父类F16不知道它是从makeF16()方法返回的F16的哪个变体。 一般设置是超类具有除创建方法之外的所有方法的实现。 create方法可以是抽象方法,也可以带有默认实现,然后由超类的其他方法调用。 正确的对象的创建是子类的责任。

简单工厂模式和静态工厂模式的区别

  • 简单工厂无法像工厂方法模式那样通过继承来生产变化的产品。

注意

  • 该模式可能会导致很多差别很小的子类。
  • 如果子类扩展了功能,那么超类将无法使用它,除非它将其转换为具体类型,然而向下转换可能在运行时失败。

参考资料

设计模式之 “工厂模式”
设计模式总结
Software Design Patterns: Best Practices for Software Developers /github educative

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×