Factory模式
- 3.1 Scenario
- 3.2 Factory Method 工厂方法
- 3.3 Factory
- 3.4 Inner Factory
- 3.5 抽象工厂Abstract Factory
- 3.6 Functional Factory
- 3.7 总结
> I had a problem and tried to use Java, now I have a ProblemFactory.
> - Old Java joke
3.1 Scenario
加入要保存一个Point
的信息, 你会先实现:
1 | struct Point |
到目前为止, 一切都好. 但是, 假如, 你又需要实现极坐标下的坐标表示. 你可能需要另一个构造函数:
1 | Point(const float r, const float theta) |
但是, 不行. 因为这两个构造函数有相同的签名.
一种做法是引入另一个枚举值, 并将其作为构造函数的另一个参数:
1 | enum class PointType |
我们的做法当然可以使用, 但是很ugly. 下面看看如何改进它:
3.2 Factory Method 工厂方法
1 | struct Point |
现在, 我们可以这样使用:
1 | auto p = Point::NewPolar(5, M_PI_4); |
3.3 Factory
我们也可以把工厂方法从类中拿出来, 放到单独的一个类中, 称作工厂, Factory.
1 | struct Point |
这个类定义有几点要说明的:
Point
的构造函数被设置为private
的, 因为我们不想让任何外部类来访问它. 这不是一个严格的要求, 但是把它作为public
会引入一些迷惑: 因为它提供了两种不同的创建对象的途径.Point
将类PointFactory
声明为友元类, 从而PointFactory
可以访问Point
的私有属性和方法.
1 | struct PointFactory |
这样, 创建对象的代码就可以写成这样:
1 | auto my_point = PointFactory::NewCartesian(3, 4); |
3.4 Inner Factory
Inner Factory指的是定义在要创建的类内部的工厂类. 一般来说, inner factory是诸如C#, Java等语言的常用手段, 因为这些语言没有friend
关键字. 但是C++并不是不能这样.
使用Inner Factory的原因是内部类可以访问外部类的私有成员, 反过来也一样, 外部类可以访问内部类的私有成员. 这意味着我们的Point
类也可以这样写:
1 | struct Point |
如果一个工厂类的创建工作只有有限的几步时, 这样做还是很方便的. 相反, 如果工厂依赖于多个类型, 尤其是当需要访问这些类的私有成员时, 内部工厂就完全没有办法了.
另外, 在这个例子中, 我们将整个工厂类放到了类的私有部分中, 并且它的构造函数还进一步被声明为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 | struct HotDrink |
对于类型Tea
, 我们可能会这么实现:
1 | struct Tea : HotDrink |
类似的, 我们也可以定义Coffee
.
下面我们可以根据类型来构造不同的饮料. 下面的代码能工作, 但是很难看:
1 | unique_ptr<HotDrink> make_drink(string type) |
这里, 关键的一点是, 不同的饮料使用不同的机制来创建. 在我们的例子中, 我们关注的是热饮, 我们可以建模:
1 | struct HotDrinkFactory |
这就是抽象工厂模式. 它只提供了抽象接口, 我们需要实现生成饮料的实际类. 例如:
1 | struct CoffeeFactory : public HotDrinkFactory |
然后, 我们考虑我们又需要定义一个更高层次的接口, 它要能支持热饮和冷饮, 比如. 我们可以定义一个工厂类DrinkFactory
. 它只需要包含指向不同工厂的引用:
1 | class DrinkFactory |
3.6 Functional Factory
这里, 当我们提到术语工厂(factory)
的时候, 我们一般是指两件事:
- 一个类, 它知道如何创建对象
- 一个函数, 当它被调用时, 会创建对象
第二种情况并不局限于典型场景下的Factory Method. 例如, 一个std::function, 它返回一个T类型的对象, 我们通常也将其称为一个Factory
而不是Factory Method
.
function可以被存储在变量中, 而不仅仅是像之前在DrinkFactory
中那样仅仅是存储一个指针.
1 | class DrinkWithVolumeFactory |
使用起来也 简单多了:
1 | inline unique_ptr<HotDrink> |
3.7 总结
- 工厂方法factory method是一个类成员函数, 用来创建对象. 它一般用于替代构造函数
- 工厂factory通常是一个单独的类, 它知道如何创建对象. 你也可以传递一个函数(例如, std::function或类似的东西), 这个参数也被称为factory
- 抽象工厂abstract factory, 正如其名, 是一个抽象类. 可以从它派生出用于创建各种对象的具体类.
Factory和Builder的主要差别在于, Factory是用于一次性创建对象的, 而Builder则是提供某一/某些精确的信息来构建对象.