传统的面向对象的方法是模板方法模式和策略模式。
模板法
第一个是文森佐的回答中描述的技术的扩展:不是编写简单的非虚拟包装器,而是编写一个包含整个算法的非虚拟函数。那些可能有所不同的部分是虚函数调用。给定实现所需的特定参数存储在提供该实现的派生类对象中。
例如。
class VoxelDrawer {
protected:
virtual void copy(Coord from, Coord to) = 0;
// any other functions you might want to change
public:
virtual ~VoxelDrawer() {}
void draw(arg) {
for (;;) {
// implement full algorithm
copy(a,b);
}
}
};
class SmoothedVoxelDrawer: public VoxelDrawer {
int radius; // algorithm-specific argument
void copy(Coord from, Coord to) {
blit(from.dx(-radius).dy(-radius),
to.dx(-radius).dy(-radius),
2*radius, 2*radius);
}
public:
SmoothedVoxelDrawer(int r) : radius(r) {}
};
战略
这很相似,但不是使用继承,而是将多态Copier
对象作为参数传递给函数。它更加灵活,因为它将您的各种复制策略与特定功能解耦,您可以在其他功能中重复使用您的复制策略。
struct VoxelCopier {
virtual void operator()(Coord from, Coord to) = 0;
};
struct SmoothedVoxelCopier: public VoxelCopier {
// etc. as for SmoothedVoxelDrawer
};
void draw_voxels(arguments, VoxelCopier ©) {
for (;;) {
// implement full algorithm
copy(a,b);
}
}
虽然比传入函数指针更整洁,但无论是模板方法还是策略都不太可能比仅传递函数指针有更好的性能:运行时多态仍然是间接函数调用。
政策
策略模式的现代 C++ 等价物是策略模式。这只是将运行时多态性替换为编译时多态性,以避免间接函数调用并启用内联
// you don't need a common base class for policies,
// since templates use duck typing
struct SmoothedVoxelCopier {
int radius;
void copy(Coord from, Coord to) { ... }
};
template <typename CopyPolicy>
void draw_voxels(arguments, CopyPolicy cp) {
for (;;) {
// implement full algorithm
cp.copy(a,b);
}
}
由于类型推导,您可以简单地调用
draw_voxels(arguments, SmoothedVoxelCopier(radius));
draw_voxels(arguments, OtherVoxelCopier(whatever));
注意。我在这里有点不一致:我曾经operator()
让我的策略调用看起来像一个常规函数,但我的策略是一个正常的方法。只要你选择一个并坚持下去,这只是一个品味问题。
CRTP模板法
最后一种机制是模板方法的编译时多态性版本,它使用了奇怪的循环模板模式。
template <typename Impl>
class VoxelDrawerBase {
protected:
Impl& impl() { return *static_cast<Impl*>(this); }
void copy(Coord from, Coord to) {...}
// *optional* default implementation, is *not* virtual
public:
void draw(arg) {
for (;;) {
// implement full algorithm
impl().copy(a,b);
}
}
};
class SmoothedVoxelDrawer: public VoxelDrawerBase<SmoothedVoxelDrawer> {
int radius; // algorithm-specific argument
void copy(Coord from, Coord to) {
blit(from.dx(-radius).dy(-radius),
to.dx(-radius).dy(-radius),
2*radius, 2*radius);
}
public:
SmoothedVoxelDrawer(int r) : radius(r) {}
};
概括
一般来说,我更喜欢策略/策略模式,因为它们的耦合度较低且重用性更好,并且仅在您要参数化的顶级算法真正固定不变的情况下(即,当您进行重构时)才选择模板方法模式现有代码或确实确定您对变异点的分析)和重用确实不是问题。
如果有多个变化轴,使用模板方法也很痛苦(也就是说,您有多个方法,例如copy
,并希望独立地改变它们的实现)。您最终会出现代码重复或混合继承。