02-工厂方法与抽象工厂模式
引子
接下来以造墙为例,探讨一下工厂方法模式。
首先,我们先定义一个墙。墙应该有:
- 底部起止二维坐标。
- 墙的海拔(相对于某个基线的高度或z坐标)。
- 墙的高度。
代码如下:
class Wall
{
protected:
Wall(Point2D start, Point2D end, int elevation, int height)
: start(start), end(end), elevation(elevation), height(height) {}
private:
Point2D start, end;
int elevation, height;
};
接下来,为了让墙更接近实际,引入材质和厚度,创建新的墙类SolidWall
:
enum class Material
{
brick, concrete
};
class SolidWall : public Wall
{
protected:
SolidWall(Point2D start, Point2D end, int elevation, int height,
int width, Material material)
: Wall(start, end, elevation, height), width(width), material(material) {}
public:
static SolidWall create_brick(Point2D start, Point2D end, int elevation, int height)
{
return {start, end, elevation, height, 120, Material::brick};
}
static unique_ptr<SolidWall> create_concrete(Point2D start, Point2D end, int elevation, int height)
{
return make_unique<SolidWall>(start, end, elevation, height, 375, Material::concrete);
}
private:
int width;
Material material;
};
我们添加了两个静态方法,用于创建两种墙,可自己决定返回什么对象。这两种静态方法都被称为 工厂方法,它们强制用户创建指定类型而非任意类型的墙。
我们现在可以造两种墙了,但还无法判断墙与墙之间的相交。我们需要跟踪记录创建好的每一堵墙才能解决这个问题,但显然在SolidWall
类里是无法完成这个任务的。该怎么办呢,请看 工厂方法模式。
工厂方法模式
概念
如图,定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类,这就是工厂方法模式的思路。
有了工厂方法模式这一概念后,我们就可以把这两个工厂方法移动到WallFactory
中,它负责生成并管理Wall
和SolidWall
:
class WallFactory
{
public:
static SolidWall create_brick(Point2D start, Point2D end, int elevation, int height)
{
const auto this_wall = new SolidWall(start, end, elevation, height. 120, Material::brick);
// 防止墙相交
for (const auto wall : walls)
{
if (auto p = wall.lock())
{
if (this_wall->intersects(*p))
{
delete this_wall;
return {};
}
}
}
shared_ptr<SolidWall> ptr(this_wall);
walls.push_back(ptr);
return ptr;
}
static unique_ptr<SolidWall> create_concrete(Point2D start, Point2D end, int elevation, int height)
{/* ... */}
private:
static vector<weak_ptr<Wall>> walls;
};
多态工厂方法
可以通过返回普通/智能指针的方式让工厂方法返回多态类型(不能返回值,因为按值传递会造成对象切割)。
例如我们定义一个枚举类WallType
用于指定要建造的墙:
enum class WallType
{
basic, solid_brick, solid_concrete
};
那么多态工厂方法可被定义如下:
static shared_ptr<Wall> create_wall(WallType type, Point2D start, Point2D end,
int elevation, int height)
{
switch (type)
{
case WallType::solid_concrete:
return make_shared<SolidWall>(start, end, elevation, height, 375, Material::concrete);
case WallType::solid_brick:
return make_shared<SolidWall>(start, end, elevation, height, 120, Material::brick);
case WallType::basic:
return shared_ptr<Wall>{new Wall(start, end, elevation, height)};
}
return {};
}
当使用多态工厂方法时,需要格外注意:调用任何没有使用关键字virtual
限定的方法都将只会得到基类中该方法所定义的行为。例如,如果Wall
和SolidWall
都重载了ostream& operator<<
,在不用dynamic_pointer_cast()
的情况下,只会看到基类Wall
的输出。
抽象工厂模式
概念
有时候我们也许会参与整个族类对象的创建,这个场景十分罕见,抽象工厂模式是一种只在复杂系统中出现的模式,如上图。它 为创建一组相关或相互依赖的对象提供一个接口,而且无需指定它们的具体类。
假设我们经营一家蜜雪冰城,有茶、咖啡等热饮。首先,定义热饮如下:
class HotDrink
{
public:
virtual void prepare(int volume) = 0;
// 其他方法省略...
};
有了抽象的热饮,就能定义热饮的 抽象工厂 了:
class HotDrinkFactory
{
public:
virtual unique_ptr<HotDrink> make() const = 0;
// 其他方法省略...
};
接下来定义一个具体的热饮,比如茶:
class Tea : public HotDrink
{
public:
void prepare(int volume) override { cout << "Hot Tea " << volume << " ml OK.\n"; }
// 其他方法省略...
};
咖啡的定义与此类似。有了具体的饮品,就能定义它们具体的工厂了。以咖啡为例:
class CoffeeFactory : public HotDrinkFactory
{
public:
unique_ptr<HotDrink> make() const override { return make_unique<Coffee>(); }
// 其他方法省略...
};
除了热饮外,还有冷饮,因此我们需要一个更高级的接口:
class DrinkFactory
{
public:
DrinkFactory()
{
hot_factories["coffee"] = make_unique<CoffeeFactory>();
hot_factories["tea"] = make_unique<TeaFactory>();
}
// 实现冷饮后就能用Drink了
unique_ptr<HotDrink> make_hot_drink(const string& name, const int volume)
{
auto drink = hot_factories[name]->make();
drink->prepare(volume);
return drink;
}
private:
map<string, unique_ptr<HotDrinkFactory>> hot_factories;
};
一些寄巧
嵌套工厂
如果准备从一开始就让工厂和对象打交道,可以创建嵌套的工厂,即 在对象内部定义工厂。这里以墙的为例:
class Wall
{
// 省略一堆变量&方法
private:
class BasicWallFactory
{
private:
BasicWallFactory() = default;
public:
shared_ptr<Wall> create(Point2D start, Point2D end, int elevation, int height)
{
return shared_ptr<Wall>(new Wall(start, end, elevation, height));
}
};
public:
static BasicWallFactory factory;
};
有关嵌套类BasicWallFactory
有以下注意点:
- 在类
Wall
外部其他地方无法直接初始化BasicWallFactory
- 这里的工厂方法不是静态的
我们接下来可以按照以下方式来使用工厂:
auto basic = Wall::factory.create({0, 0}, {5000, 0}, 0, 3000);
函数式工厂
当我们使用术语“工厂”时,通常指的是以下两个概念之一:
- 指一个类,这个类可以创建对象(就是上边的一堆例子)
- 指一个 函数,当调用这个函数时,可以创建一个对象。
如下例所示,这个函数也是一种工厂:
template <typename T>
void consturct(function<T()> f)
{
T t = f();
// 使用t.....
}
将上面DrinkFactory
的例子做修改,让它应用函数式工厂:
class DrinkFactory
{
public:
DrinkFactory()
{
factories["tea"] = []{ return make_unique<Tea>(); }
factories["coffee"] = []{ return make_unique<Coffee>(); }
}
unique_ptr<HotDrink> make_drink(const string& name) { return factories[name](); }
private:
map<string, function<unique_ptr<HotDrink>()>> factories;
};
总结
使用工厂的好处包括:
- 可以直到已经创建的特定类型的对象数量
- 可以修改或完全替换整个对象的创建过程
- 如果使用
shared_ptr
,还能获取该对象在其他地方被引用的数量
参考资料
- 《C++20设计模式 可复用的面向对象设计方法》
- 工厂方法模式 · Design Patterns (hypc-pub.github.io)