> I had a problem and tried to use Java, now I have a ProblemFactory.   
>                                  - Old Java joke

3.1 Scenario

加入要保存一个Point的信息, 你会先实现:

1
2
3
4
5
6
7
8
struct Point
{
Point(const float x, const float y)
: x{x}, y{y}
{}

float x, y;
};

到目前为止, 一切都好. 但是, 假如, 你又需要实现极坐标下的坐标表示. 你可能需要另一个构造函数:

1
2
3
4
5
Point(const float r, const float theta)
{
x = r * cos(theta);
y = r * sin(theta);
}

但是, 不行. 因为这两个构造函数有相同的签名.

一种做法是引入另一个枚举值, 并将其作为构造函数的另一个参数:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum class PointType
{
cartesian,
polar
}

Pointer(float a, float b, PointType type=PointType::cartesian)
{
if( type == PointType::cartesian)
{
...
}
else
{
...
}
}

我们的做法当然可以使用, 但是很ugly. 下面看看如何改进它:

3.2 Factory Method 工厂方法

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Point
{
protected:
Point(const float x, const float y)
: x{x}, y{y}
{}

public:
static Point NewCartesian(float x, float y)
{
return {x, y};
}

static Point NewPolar(float r, float theta)
{
return { r * cos(theta), r * sin(theta) };
}

//....
}

现在, 我们可以这样使用:

1
auto p = Point::NewPolar(5, M_PI_4);

3.3 Factory

我们也可以把工厂方法从类中拿出来, 放到单独的一个类中, 称作工厂, Factory.

{.line-numbers}
1
2
3
4
5
6
7
struct Point
{
float x, y;
friend class PointFactory;
private:
Point(float x, float y) : x(x), y(x) {}
};

这个类定义有几点要说明的:

  1. Point的构造函数被设置为private的, 因为我们不想让任何外部类来访问它. 这不是一个严格的要求, 但是把它作为public会引入一些迷惑: 因为它提供了两种不同的创建对象的途径.

  2. Point将类PointFactory声明为友元类, 从而PointFactory可以访问Point的私有属性和方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
struct PointFactory
{
static Point NewCartesian(float x, float y)
{
return Point{x, y};
}

static Point NewPolar(float r, float theta)
{
return { r * cos(theta), r * sin(theta) };
}

}

这样, 创建对象的代码就可以写成这样:

1
auto my_point = PointFactory::NewCartesian(3, 4);

3.4 Inner Factory

Inner Factory指的是定义在要创建的类内部的工厂类. 一般来说, inner factory是诸如C#, Java等语言的常用手段, 因为这些语言没有friend关键字. 但是C++并不是不能这样.

使用Inner Factory的原因是内部类可以访问外部类的私有成员, 反过来也一样, 外部类可以访问内部类的私有成员. 这意味着我们的Point类也可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Point
{
private:
Point(float x, float y) : x(x), y(y) {}

struct PointFactory
{
private:
PointFactory(){}
public:
static Point NewCartesian(float x, float y)
{
return {x, y};
}
static Point NewPolar(float r, float theta)
{
return { r * cos(theta), r * sin(theta)};
}
}

public:
float x, y;
static PointFactory Factory;
}

如果一个工厂类的创建工作只有有限的几步时, 这样做还是很方便的. 相反, 如果工厂依赖于多个类型, 尤其是当需要访问这些类的私有成员时, 内部工厂就完全没有办法了.

另外, 在这个例子中, 我们将整个工厂类放到了类的私有部分中, 并且它的构造函数还进一步被声明为private的. 本来, 即使我们把工厂类暴露出来, Point::PointFactory的用法也是很晦涩的. 相反, 我们定义了一个称为Factory的静态成员, 通过它我们可以这样使用:

1
auto pp = Point::Factory.NewCartesian(2,3);

如果, 你不愿意混合使用::., 你也可以在各处都使用:::

  • 让工厂类成为public的, 从而可以这样使用:

    Point::PointFactory::NewXxx(...);

  • 如果你不愿意两次出Point的字样, 你也可以在Point类中使用typedef PointFactory Factory, 这样就可以这样写代码:

    Point::Factory::NewXxx(...);

是否使用内部工厂在很大程度上取决于你打算如何组织自己的代码. 然而, 一般来说, 将刚刚给工厂从原始对象中暴露出来可以极大的改善API的可用性. 比如说, 我发现构造函数被私有化了的类Point, 我从何得知该如何使用这个类呢? 我不能, 除非在什么地方给了我有意义的提示.

3.5 抽象工厂Abstract Factory

有时我们会遇到需要创建一族对象的情况.

实际上, 这是很少见的一种场景, 一般只会在复杂系统中才会遇到.

考虑一个简单的场景: 你在经营一个咖啡馆, 它提供茶和咖啡两种饮料. 这两种热饮有完全不同的外观, 我们可以使用不同的工厂来建模.

首先, 我们定义热饮类型HotDrink:

1
2
3
4
struct HotDrink
{
virtual void prepare(int volumn) = 0;
};

对于类型Tea, 我们可能会这么实现:

1
2
3
4
5
6
7
struct Tea : HotDrink
{
virtual void prepare(int volumn) override
{
cout << "Take tea bag ... " << endl;
}
}

类似的, 我们也可以定义Coffee.

下面我们可以根据类型来构造不同的饮料. 下面的代码能工作, 但是很难看:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unique_ptr<HotDrink> make_drink(string type)
{
unique_ptr<HotDrink> drink;
if( type == "tea")
{
drink = make_unique<Tea>();
drink->prepare(200);
}
else
{
drink = make_unique<Coffee>();
drink->prepare(50);
}
return drink;
}

这里, 关键的一点是, 不同的饮料使用不同的机制来创建. 在我们的例子中, 我们关注的是热饮, 我们可以建模:

1
2
3
4
struct HotDrinkFactory
{
virtual unique_ptr<HotDrink> make() const = 0;
}

这就是抽象工厂模式. 它只提供了抽象接口, 我们需要实现生成饮料的实际类. 例如:

{.line-numbers}
1
2
3
4
5
6
7
struct CoffeeFactory : public HotDrinkFactory
{
unique_ptr<HotDrink> make() const override
{
return make_unique<Coffee>();
}
}

然后, 我们考虑我们又需要定义一个更高层次的接口, 它要能支持热饮和冷饮, 比如. 我们可以定义一个工厂类DrinkFactory. 它只需要包含指向不同工厂的引用:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class DrinkFactory
{
map<string, unique_ptr<HotDrinkFactory>> hot_factories;
public:
DrinkFactory()
{
hot_factories["coffee"] = make_unique<CoffeeFactory>();
hot_factories["tea"] = make_unique<TeaFactory>();
}

unique_ptr<HotDrink> make_drink( const string& name)
{
auto drink = hot_factories[name]->make();
drink->prepare(200); // oops!
return drink;
}
}

3.6 Functional Factory

这里, 当我们提到术语工厂(factory)的时候, 我们一般是指两件事:

  • 一个类, 它知道如何创建对象
  • 一个函数, 当它被调用时, 会创建对象

第二种情况并不局限于典型场景下的Factory Method. 例如, 一个std::function, 它返回一个T类型的对象, 我们通常也将其称为一个Factory而不是Factory Method.

function可以被存储在变量中, 而不仅仅是像之前在DrinkFactory中那样仅仅是存储一个指针.

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
class DrinkWithVolumeFactory
{
map<string, function<unique_ptr<HotDrink>()>> factories;
public:
DrinkWithVolumeFactory()
{
factories["tea"] = []{
auto tea = make_unique<Tea>();
tea->prepare(200);
return tea;
};
}
}

使用起来也 简单多了:

1
2
3
4
5
inline unique_ptr<HotDrink>
DrinkWithVolumeFactory::make_drink(const string& name)
{
return factories[name]();
}

3.7 总结

  • 工厂方法factory method是一个类成员函数, 用来创建对象. 它一般用于替代构造函数
  • 工厂factory通常是一个单独的类, 它知道如何创建对象. 你也可以传递一个函数(例如, std::function或类似的东西), 这个参数也被称为factory
  • 抽象工厂abstract factory, 正如其名, 是一个抽象类. 可以从它派生出用于创建各种对象的具体类.

FactoryBuilder的主要差别在于, Factory是用于一次性创建对象的, 而Builder则是提供某一/某些精确的信息来构建对象.