首先我会说你应该尝试使用接口而不是抽象类。抽象类将子类耦合到超类的实现。在像 Java 这样的语言中,即使超类不打算这样做,子类也可以覆盖任何方法,而且大多数人并不总是用“不覆盖”来限定他们的方法。
在最低级别,抽象方法在编译时为您提供两种具体的保护:
在列出抽象方法的用例之前,我只想说“通用功能”不是抽象基类的好理由。如果您需要通用功能,只需创建一个具有通用方法的类,并让各个类按照它们认为合适的方式调用这些函数。
那么什么时候你会使用抽象类呢?以下是一些示例:
模板方法
在模板方法模式中,您拥有所有功能,但只有一个内部方面是多态的,因此您拥有覆盖该特定方面的子类。
例如,如果你正在实现一个缓存,但缓存失效策略是多态的,你可能有一个抽象的 invalidate() 方法,该方法在内部被其他方法调用,但要实现 invalidate() 则取决于子类。
如果有首选的默认缓存失效策略,那么invalidate() 可以实现该默认值。但是,如果该默认值在某些情况下完全具有破坏性,那么它不应该是默认值 - 它应该是抽象的,并且应该强制创建缓存的代码显式选择失效策略。
这也可以通过将Invalidator类传递给构造函数来实现(策略模式),但是如果失效逻辑需要调用缓存的方法,最好将这些方法保护起来并从子类中调用它们(即模板方法模式)。
其他方法的默认实现
在接口不能有默认方法的语言中(例如 Java 7),您可以使用抽象类来模拟它。所有接口方法都是抽象的,但默认方法是常规的公共方法。
通用接口和功能
这只是模板方法模式的一个更通用的版本。不同之处在于多态方法是 API 的一部分。
如果您的常用功能与您要公开的功能有很多重叠,并且您不想要大量样板代码,那么您可以使用抽象类。例如:
interface File {
abstract Buffer read(int size);
abstract void write(Buffer buf);
abstract long getSize();
abstract void setSize();
// ... get/set creation time, get/set modification time, get
// file type etc.
abstract long getOwner();
abstract void setOwner(long owner);
}
abstract class AbstractFile extends File {
DataMap dataMap;
MetadataMap metaMap;
protected getDiskMap() { return dataMap; }
protected getMetaMap() { return metaMap; }
public Buffer read(int size) { /* loop here */ }
public void write(Buffer buf) { /* loop here */ }
public long getSize() { /* logic */ }
public void setSize() { /* logic */ }
// ... implementation of get/set creation time, get/set modification
// time, get file type etc.
}
abstract class HardDriveFile extends AbstractFile {
OwnershipMap ownerMap;
abstract long getOwner() { /* logic */ }
abstract void setOwner(long owner) { /* logic */ }
}
abstract class ThumbDriveFile extends AbstractFile {
// thumb drives have no ownership
abstract long getOwner() { return 0; }
abstract void setOwner(long owner) { /* no-op */ }
}
abstract class SomeOtherfile extends AbstractFile {
...
}
如果我们砍掉中间人,让HardDriveFile 和ThumbDriveFile(可能还有其他类型的文件)实现File 并拼出所有常用方法,每个方法调用某个常用类的方法,我们会得到山和大量的样板。所以我们从一个抽象基类继承,它具有我们想要专门化的抽象方法(例如,基于所有权映射的存在)。
最幼稚的做法是将File 和AbstractFile 组合成一个类,您可以在其中获得抽象方法getOwner() 和setOwner(),但最好将抽象类隐藏在后面实际接口,以防止 API 的使用者与抽象类之间的耦合。