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

时间:2010-06-28 17:45:41

标签: java c# design-patterns architecture abstract-base-class

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

8 个答案:

答案 0 :(得分:22)

如果需要实现特定方法,请使用接口。如果存在可以拉出的共享逻辑,请使用抽象基类。如果基本功能集完全独立,那么您可以使用concreate类作为基础。抽象基类和接口不能直接实例化,这是其中一个优点。如果你可以使用具体的类型,那么你需要重写方法,并且它具有“代码味道”。

答案 1 :(得分:5)

程序接口,而不是实现与抽象和具体类几乎没有关系。还记得template method pattern吗?类,抽象或具体,是实现细节。

使用抽象类而不是具体类的原因是你可以在不实现它们的情况下调用方法,而是让它们实现为子类而不是。

对接口进行编程是另一回事 - 它定义了你的API所做的,而不是 它是如何做的。这由接口表示。

注意一个关键区别 - 您可以使用protected abstract方法,这意味着这是实现细节。但是所有接口方法都是公共的 - 这是API的一部分。

答案 2 :(得分:1)

是的,尽管您也可以使用界面强制类实现特定方法。

使用抽象类而不是具体类的另一个原因是显然无法实例化抽象类。有时候你也不希望这种情况发生,所以抽象类是要走的路。

答案 3 :(得分:1)

首先,战略模式应该几乎不会在现代C#中使用。它主要适用于Java等语言,它们不支持函数指针,委托或一流函数。您将在IComparer等接口中的旧版C#中看到它。

对于抽象基类与混凝土类,Java中的答案总是“在这种情况下什么效果更好?”如果您的策略可以共享代码,那么请务必让他们这样做。

设计模式不是关于如何做某事的说明。它们是对我们已经完成的事情进行分类的方法。

答案 4 :(得分:1)

抽象基类通常用于设计者想要强制构造模式的场景,其中所有类以相同的方式执行某些任务,而其他行为依赖于子类。 例如:

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行为。

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

  1. 基类不存在子类=>基类只是抽象的,而且它可以'实例化。
  2. 基类不能完全或具体实现方法=>方法的实现是基类是不完整的,只有子类可以提供完整的实现。
  3. 基类为方法实现提供了一个模板,但仍然依赖于Concrete类来完成方法实现 - Template_method_pattern
  4. 一个简单的例子来说明上述要点

    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()方法更改为模板方法。

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

    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的类。