就我个人而言,我几乎不需要编写抽象类。
大多数时候我看到抽象类被(误用)了,这是因为抽象类的作者使用的是“模板方法”模式。
“模板方法”的问题在于它几乎总是在某种程度上是可重入的——“派生”类不仅知道它正在实现的基类的“抽象”方法,还知道它的公共方法基类,尽管大多数时候它不需要调用它们。
(过于简化)示例:
abstract class QuickSorter
{
public void Sort(object[] items)
{
// implementation code that somewhere along the way calls:
bool less = compare(x,y);
// ... more implementation code
}
abstract bool compare(object lhs, object rhs);
}
所以在这里,这个类的作者编写了一个通用算法,并打算通过提供他们自己的“钩子”来“专门化”它,让人们使用它——在这种情况下,是一个“比较”方法。
所以预期的用法是这样的:
class NameSorter : QuickSorter
{
public bool compare(object lhs, object rhs)
{
// etc.
}
}
问题在于您将两个概念过度耦合在一起:
- 比较两个项目的一种方式(应该先去哪个项目)
- 一种对项目进行排序的方法(即快速排序与合并排序等)
在上面的代码中,理论上,“比较”方法的作者可以重新进入回调超类的“排序”方法......即使在实践中他们永远不会想要或需要这样做。
您为这种不必要的耦合付出的代价是很难更改超类,而且在大多数 OO 语言中,无法在运行时更改它。
另一种方法是改用“策略”设计模式:
interface IComparator
{
bool compare(object lhs, object rhs);
}
class QuickSorter
{
private readonly IComparator comparator;
public QuickSorter(IComparator comparator)
{
this.comparator = comparator;
}
public void Sort(object[] items)
{
// usual code but call comparator.Compare();
}
}
class NameComparator : IComparator
{
bool compare(object lhs, object rhs)
{
// same code as before;
}
}
现在请注意:我们所拥有的只是接口,以及这些接口的具体实现。实际上,您真的不需要任何其他东西来进行高级 OO 设计。
为了“隐藏”我们已经通过使用“QuickSort”类和“NameComparator”实现“名称排序”这一事实,我们可能仍会在某处编写工厂方法:
ISorter CreateNameSorter()
{
return new QuickSorter(new NameComparator());
}
任何时候只要你有一个抽象类,你就可以这样做......即使在基类和派生类之间存在自然的可重入关系时,将它们显式化通常也是值得的。
最后一个想法:我们上面所做的只是通过使用“QuickSort”函数和“NameComparison”函数来“组合”一个“NameSorting”函数......在函数式编程语言中,这种编程风格变得更加均匀更自然,代码更少。