在阅读了最优秀的书《Head First Design Patterns》之后,我开始向我的同事宣传模式和设计原则的好处。在赞美我最喜欢的模式——策略模式——的优点时,我被问到一个让我停顿的问题。当然,策略使用继承和组合,当一位同事问“为什么使用抽象基类而不是具体类?”时,我正在谈论“程序到接口(或超类型)而不是实现”的长篇大论.
我只能想出“好吧,你强制你的子类实现抽象方法并阻止它们实例化 ABC”。但老实说,这个问题让我措手不及。
8 回答
如果您需要实现特定方法,请使用接口。如果有可以拉出来的共享逻辑,就使用抽象基类。如果基本功能集本身是完整的,那么您可以使用 concreate 类作为基础。抽象基类和接口不能直接实例化,这是优点之一。如果您可以使用具体类型,那么您需要执行覆盖方法,这对它有“代码味道”。
程序到接口,而不是实现,与抽象类和具体类几乎没有关系。还记得模板方法模式吗?类,无论是抽象的还是具体的,都是实现细节。
使用抽象类而不是具体类的原因是您可以在不实现它们的情况下调用方法,而是将它们留给子类来实现。
对接口进行编程是另一回事——它定义了你的 API 做什么,而不是它是如何做的。这由接口表示。
注意一个关键区别——你可以有protected abstract
方法,这意味着这是实现细节。但是所有接口方法都是公共的——API 的一部分。
是的,尽管您也可以使用接口来强制类实现特定方法。
使用抽象类而不是具体类的另一个原因是抽象类显然不能被实例化。有时你也不希望这种情况发生,所以抽象类是要走的路。
首先,策略模式几乎不应该在现代 C# 中使用。它主要适用于不支持函数指针、委托或一等函数的 Java 等语言。您将在 IComparer 等接口的旧版 C# 中看到它。
至于抽象基类与具体类,Java 中的答案始终是“在这种情况下什么效果更好?” 如果您的策略可以共享代码,那么一定要让他们这样做。
设计模式不是关于如何做某事的说明。它们是对我们已经完成的事情进行分类的方法。
抽象基类通常用于设计人员想要强制一种架构模式的场景,其中某些任务将由所有类以相同的方式执行,而其他行为依赖于子类。例子:
public abstract class Animal{
public void digest(){
}
public abstract void sound(){
}
}
public class Dog extends Animal{
public void sound(){
System.out.println("bark");
}
}
策略模式要求设计者在有一系列行为的情况下使用组合行为。
如果客户端依赖于“隐含的行为契约”,它会针对实现和无保证的行为进行编程。在遵循合同的同时覆盖该方法只会暴露客户端中的错误,而不是导致它们。
OTOH,如果所讨论的方法是非虚拟的,那么假设不存在的合同的错误不太可能导致问题 - 即,覆盖它不会导致问题,因为它不能被覆盖。只有更改原始方法的实现(同时仍然遵守合同)才能破坏客户端。
基类应该是抽象的还是具体的问题很大程度上取决于恕我直言,仅实现类中所有对象共有的行为的基类对象是否有用。考虑一个 WaitHandle。对其调用“等待”将导致代码阻塞,直到满足某些条件,但没有常见的方法告诉 WaitHandle 对象其条件得到满足。如果可以实例化“WaitHandle”,而不是只能实例化派生类型的实例,那么这样的对象将不得不永远等待,或者永远等待。后一种行为将毫无用处。前者可能很有用,但几乎可以通过静态分配的 ManualResetEvent 来实现(我认为后者浪费了一些资源,但如果它'
在许多情况下,我认为我的偏好是使用对接口而不是抽象基类的引用,但为接口提供一个提供“模型实现”的基类。因此,任何地方都会使用对 MyThing 的引用,人们会提供对“iMyThing”的引用。很可能 99%(甚至 100%)的 iMyThing 对象实际上是一个 MyThing,但如果有人需要一个从其他东西继承的 iMyThing 对象,可以这样做。
在以下场景中首选抽象基类:
- 没有子类就不能存在基类 => 基类只是抽象的,它不能被实例化。
- 基类不能有方法的完整或具体实现=>方法的实现是基类不完整,只有子类可以提供完整的实现。
- 基类为方法实现提供了一个模板,但它仍然依赖于具体类来完成方法实现 - Template_method_pattern
一个简单的例子来说明以上几点
Shape
是抽象的,没有像Rectangle
. 由于不同的形状有不同的公式Shape
,因此无法在课堂上绘制 a 。Shape
处理场景的最佳选择:将draw()
实现留给子类
abstract class Shape{
int x;
int y;
public Shape(int x,int y){
this.x = x;
this.y = y;
}
public abstract void draw();
}
class Rectangle extends Shape{
public Rectangle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Rectangle using x and y : length * width
System.out.println("draw Rectangle with area:"+ (x * y));
}
}
class Triangle extends Shape{
public Triangle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Triangle using x and y : base * height /2
System.out.println("draw Triangle with area:"+ (x * y) / 2);
}
}
class Circle extends Shape{
public Circle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Circle using x as radius ( PI * radius * radius
System.out.println("draw Circle with area:"+ ( 3.14 * x * x ));
}
}
public class AbstractBaseClass{
public static void main(String args[]){
Shape s = new Rectangle(5,10);
s.draw();
s = new Circle(5,10);
s.draw();
s = new Triangle(5,10);
s.draw();
}
}
输出:
draw Rectangle with area:50
draw Circle with area:78.5
draw Triangle with area:25
draw()
以上代码涵盖了第1点和第2点。如果基类有一些实现并调用子类方法来完成draw()
功能,您可以将方法更改为模板方法。
现在与模板方法模式相同的示例:
abstract class Shape{
int x;
int y;
public Shape(int x,int y){
this.x = x;
this.y = y;
}
public abstract void draw();
// drawShape is template method
public void drawShape(){
System.out.println("Drawing shape from Base class begins");
draw();
System.out.println("Drawing shape from Base class ends");
}
}
class Rectangle extends Shape{
public Rectangle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Rectangle using x and y : length * width
System.out.println("draw Rectangle with area:"+ (x * y));
}
}
class Triangle extends Shape{
public Triangle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Triangle using x and y : base * height /2
System.out.println("draw Triangle with area:"+ (x * y) / 2);
}
}
class Circle extends Shape{
public Circle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Circle using x as radius ( PI * radius * radius
System.out.println("draw Circle with area:"+ ( 3.14 * x * x ));
}
}
public class AbstractBaseClass{
public static void main(String args[]){
Shape s = new Rectangle(5,10);
s.drawShape();
s = new Circle(5,10);
s.drawShape();
s = new Triangle(5,10);
s.drawShape();
}
}
输出:
Drawing shape from Base class begins
draw Rectangle with area:50
Drawing shape from Base class ends
Drawing shape from Base class begins
draw Circle with area:78.5
Drawing shape from Base class ends
Drawing shape from Base class begins
draw Triangle with area:25
Drawing shape from Base class ends
一旦你决定你必须作为方法abstract
,你有两个选择:用户interface
或abstract
类。您可以在其中声明您的方法并将类interface
定义为实现.abstract
interface