繁体   English   中英

抽象基类与具体类作为超类

[英]Abstract Base Class vs. Concrete Class as a SuperType

在阅读了最优秀的“Head First Design Patterns”一书之后,我开始向同事们传播模式和设计原则的好处。 在颂扬我最喜欢的模式的优点 - 策略模式 - 我被问到一个让我停顿的问题。 当然,策略使用继承和组合,当我的同事问“为什么使用抽象基类而不是具体的类?”时,我在其中一个关于“程序到接口(或超类型)而不是实现”的长篇大论。 。
我只能提出“你强迫你的子类实现抽象方法并阻止它们实例化ABC”。 但说实话,这个问题让我想起了gaurd。 这些是在我的层次结构顶部使用抽象基类而不是具体类的唯一好处吗?

如果需要实现特定方法,请使用接口。 如果存在可以拉出的共享逻辑,请使用抽象基类。 如果基本功能集完全独立,那么您可以使用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");
}
}

Stratergy模式要求设计师在有行为的alogirthms家族的情况下使用Compositional行为。

如果客户依赖于“隐含行为合同[s]”,则会根据实施情况和无担保行为进行编程。 遵循合同时覆盖方法只会暴露客户端中的错误,而不会导致错误。

OTOH,如果所讨论的方法是非虚拟的,那么假设不存在的合同的错误就不太可能导致问题 - 即,覆盖它不会导致问题,因为它不能被覆盖。 只有当原始方法的实现发生变化(同时仍然遵守合同)时才能破坏客户端。

基类应该是抽象的还是具体的问题在很大程度上依赖于IMHO是否只实现类中所有对象共有的行为的基类对象是有用的。 考虑一下WaitHandle。 对它进行“等待”将导致代码阻塞直到满足某些条件,但是没有通用的方法告诉WaitHandle对象它的条件是否满足。 如果可以实例化“WaitHandle”,而不是只能实例化派生类型的实例,那么这样的对象必须永远不会等待,或者总是等待永远。 后一种行为将毫无用处; 前者可能有用,但几乎可以通过静态分配的ManualResetEvent实现(我认为后者浪费了一些资源,但如果静态分配,则总资源损失应该是微不足道的)。

在许多情况下,我认为我的偏好是使用对接口的引用而不是对抽象基类的引用,但是为接口提供了一个提供“模型实现”的基类。 因此,任何人都会使用MyThing的引用,一个人会提供对“iMyThing”的引用。 很可能99%(甚至100%)的iMyThing对象实际上是一个MyThing,但是如果有人需要拥有一个继承其他东西的iMyThing对象,那么就可以这样做。

首选以下场景中的抽象基类:

  1. 基类不能与子类一起存在=>基类只是抽象的,不能实例化。
  2. 基类不能具有方法的完整或具体实现=>方法的实现是基类是不完整的,只有子类可以提供完整的实现。
  3. 基类为方法实现提供了一个模板,但它仍然依赖于Concrete类来完成方法实现 - Template_method_pattern

一个简单的例子来说明上述观点

Shape是抽象的,没有像Rectangle这样的混凝土形状就不能存在。 由于不同的形状具有不同的公式,因此无法在Shape类中实现绘制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

上面的代码涵盖了第1点和第2点。如果基类有一些实现并调用子类方法来完成draw()函数,则可以将draw()方法更改为模板方法。

现在使用Template方法模式的相同示例:

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 ,您有两个选择:用户interfaceabstract类。 您可以在interface声明方法,并将abstract类定义为实现interface类。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM