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

发布于 2024-09-07 12:35:41 字数 260 浏览 6 评论 0原文

读完最优秀的书《Head First Design Patterns》后,我开始向同事宣传模式和设计原则的好处。在赞扬我最喜欢的模式(策略模式)的优点时,我被问了一个问题,这让我停了下来。当然,策略使用继承和组合,当一位同事问“为什么使用抽象基类而不是具体类?”时,我正在长篇大论地谈论“针对接口(或超类型)而不是实现进行编程”。 .
我只能想出“好吧,你强迫你的子类实现抽象方法并阻止它们实例化 ABC”。但说实话,这个问题让我措手不及。这些是在层次结构顶部使用抽象基类相对于具体类的唯一好处吗?

After reading the most excellent book "Head First Design Patterns", I began proselytizing to my colleagues the benefits of patterns and design principles. While extolling the virtues of my favorite pattern - Strategy Pattern - I was asked a question which gave me pause. Strategy, of course, uses inheritance and composition and I was on one of my tirades about "program to an interface (or supertype) not an implementation", when a colleague asked "why use an abstract base class instead of a concrete class?".
I could only come up with "well you force your subclasses to implement abstract methods and prevent them from instantiating the ABC". But to be honest the question caught me off gaurd. Are these the only benefits of using an abstract base class over a concrete class at the top of my hierarchy?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

冷夜 2024-09-14 12:35:41

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

If you need specific methods to be implemented, then use an Interface. If there is shared logic that can be pulled out, use an abstract base class. If the base set of functionality is complete on its own, then you can use a concreate class as the base. An abstract base class, and an Interface cannot be instantiated directly, and that is one of the advantages. If you can use a concrete type, then you need to do override methods, and that has a "code smell" to it.

好听的两个字的网名 2024-09-14 12:35:41

针对接口编程,而不是针对实现与抽象类和具体类关系不大。还记得模板方法模式吗?类,无论是抽象的还是具体的,都是实现细节。

使用抽象类而不是具体类的原因是,您可以调用方法而不实现它们,而是将它们留给子类来实现。

对接口进行编程是另一回事 - 它定义 API 的作用,而不是定义 API 的作用方式。这是通过接口来表示的。

请注意一个关键区别 - 您可以拥有受保护的抽象方法,这意味着这是实现细节。但所有接口方法都是公共的——API 的一部分。

Program to interface, not to implementation has little to do with abstract and concrete classes. Remember the template method pattern? Classes, abstract or concrete, are the implementation details.

And the reason to use abstract classes instead of concrete classes is that you can invoke methods without implementing them, but by leaving them to be implemented to subclasses instead.

Programming to an interface is a different thing - it is defining what your API does, not how it does it. And this is denoted by interfaces.

Note one key difference - you can have protected abstract methods, which means that this is implementation detail. But all interface methods are public - part of the API.

归属感 2024-09-14 12:35:41

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

使用抽象类而不是具体类的另一个原因是抽象类显然无法实例化。有时您也不希望这种情况发生,因此抽象类是最佳选择。

Yes, although you could also use an interface to force a class to implement specific methods.

Another reason for using an abstract class as opposed to a concrete class is that an abstract class obviously can't be instantiated. Sometimes you also wouldn't want this to happen, so an abstract class is the way to go.

似最初 2024-09-14 12:35:41

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

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

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

First of all, the Strategy Pattern should almost never be used in modern C#. It is mainly for languages like Java that don't support function pointers, delegates, or first-class functions. You will see it in older versions of C# in interfaces such as IComparer.

As for Abstract Base Class vs. Concrete Class, the answer in Java is always "What works better in this situation?" If your strategies can share code, then by all means let them do so.

Design patterns are not instructions on how to do something. They are ways to categorize things that we have already done.

苍风燃霜 2024-09-14 12:35:41

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

public abstract class Animal{

public void digest(){

}

public abstract void sound(){

}
}

public class Dog extends Animal{
public void sound(){
    System.out.println("bark");
}
}

策略模式要求设计者在行为存在一系列算法的情况下使用组合行为。

Abstract base classes are usually used in scenarios where the designer wants to force an architectural pattern where certain tasks are to be carried out in the same manner by all the classes while other behaviours are dependent on the subclasses.
example:

public abstract class Animal{

public void digest(){

}

public abstract void sound(){

}
}

public class Dog extends Animal{
public void sound(){
    System.out.println("bark");
}
}

Stratergy pattern asks designers to use Compositional behaviour for cases where there are families of alogirthms for a behaviour.

暮光沉寂 2024-09-14 12:35:41

如果客户端依赖于“隐含行为契约”,​​则它是针对实现和不受保证的行为进行编程的。在遵循契约的同时重写该方法只会暴露客户端中的错误,而不是导致错误。

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

If the client relies on "implied behavior contract[s]", it is programmed against an implementation and against unguaranteed behavior. Overriding the method while following the contract only exposes bugs in the client, not causes them.

OTOH, the mistake of assuming contracts that aren't there is less likely to cause problems if the method in question is non-virtual--i.e., overriding it cannot cause problems because it cannot be overridden. Only if the implementation of the original method is changed (while still obeying the contract) can it break the client.

汹涌人海 2024-09-14 12:35:41

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

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

The question of whether a base class should be abstract or concrete depends IMHO largely on whether a base class object which implemented only behaviors that were common to all objects in the class would be useful. Consider a WaitHandle. Calling "wait" upon it will cause code to block until some condition is satisfied, but there's no common way of telling a WaitHandle object that its condition is satisfied. If one could instantiate a "WaitHandle", as opposed to only being able to instantiate instances of derived types, such an object would have to either never wait, or always wait forever. The latter behavior would be pretty useless; the former might have been useful, but could be achieved almost as well with a statically-allocated ManualResetEvent (I think the latter wastes a few resources, but if it's statically allocated the total resource loss should be trivial).

In many cases, I think my preference would be to use references to an interface rather than to an abstract base class, but provide with the interface a base class which provides a "model implementation". So any place one would use a reference to a MyThing, one would supply a reference to "iMyThing". It may well be that 99% (or even 100%) of iMyThing objects are actually a MyThing, but if someone ever needs to have an iMyThing object which inherits from something else, one could do so.

最美不过初阳 2024-09-14 12:35:41

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

  1. 基类不能没有子类而存在=>基类只是抽象的,无法实例化。
  2. 基类不能具有方法的完整或具体实现 =>基类方法的实现是不完整的,只有子类可以提供完整的实现。
  3. 基类提供了方法实现的模板,但仍然依赖具体类来完成方法实现 -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()功能。

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

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类。您可以在接口中声明方法,并将抽象类定义为实现接口的类。

Prefer abstract base classes in below scenarios:

  1. A base class can't exist with out a sub class => the base class is simply abstract and it can' t be instantiated.
  2. A base class can't have full or concrete implementation of a method => Implementation of a method is base class is incomplete and only sub classes can provide complete implementation.
  3. Base class provides a template for method implementation but it still depends on Concrete class to complete the method implementation -Template_method_pattern

A simple example to illustrate above points

Shape is abstract and it can't exist without Concrete shape like Rectangle. Drawing a Shape can't be implemented at Shape class since different shapes have different formulas. The best option to handle scenario : leave draw() implementation to sub-classes

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();
    }
}

output:

draw Rectangle with area:50
draw Circle with area:78.5
draw Triangle with area:25

Above code covers point 1 and point 2. You can change draw() method as template method if base class has some implementation and calls sub-class method to complete draw() function.

Now same example with Template method pattern:

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();
    }
}

output:

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

Once you have decided that you have to make as method abstract, you have two options : Either user interface or abstract class. You can declare your methods in interface and define abstract class as class implementing the interface.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文