抽象基类与作为超类型的具体类
读完最优秀的书《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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
如果您需要实现特定方法,请使用接口。如果存在可以抽出的共享逻辑,请使用抽象基类。如果基本功能集本身是完整的,那么您可以使用 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.
针对接口编程,而不是针对实现与抽象类和具体类关系不大。还记得模板方法模式吗?类,无论是抽象的还是具体的,都是实现细节。
使用抽象类而不是具体类的原因是,您可以调用方法而不实现它们,而是将它们留给子类来实现。
对接口进行编程是另一回事 - 它定义 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.是的,尽管您也可以使用接口来强制类实现特定方法。
使用抽象类而不是具体类的另一个原因是抽象类显然无法实例化。有时您也不希望这种情况发生,因此抽象类是最佳选择。
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.
首先,策略模式几乎不应该在现代 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.
抽象基类通常用于设计者想要强制采用一种架构模式的场景,其中某些任务由所有类以相同的方式执行,而其他行为依赖于子类。
例如:
策略模式要求设计者在行为存在一系列算法的情况下使用组合行为。
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:
Stratergy pattern asks designers to use Compositional behaviour for cases where there are families of alogirthms for a behaviour.
如果客户端依赖于“隐含行为契约”,则它是针对实现和不受保证的行为进行编程的。在遵循契约的同时重写该方法只会暴露客户端中的错误,而不是导致错误。
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.
基类应该是抽象的还是具体的问题很大程度上取决于仅实现类中所有对象共有的行为的基类对象是否有用。考虑一个 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.
在以下场景中首选抽象基类:
一个简单的例子来说明以上几点
Shape
是抽象的,如果没有像Rectangle
这样的具体形状,它就不可能存在。绘制Shape
无法在Shape
类中实现,因为不同的形状有不同的公式。处理场景的最佳选择:将draw()
实现留给子类输出:
上面的代码涵盖了第 1 点和第 2 点。您可以将
draw()
方法更改为模板方法如果基类有一些实现并调用子类方法来完成draw()
功能。现在使用模板方法模式的相同示例:
输出:
一旦您决定必须将其作为方法
abstract
,您有两个选择:用户interface
或abstract类。您可以在接口中声明方法,并将抽象类定义为实现接口的类。
Prefer abstract base classes in below scenarios:
A simple example to illustrate above points
Shape
is abstract and it can't exist without Concrete shape likeRectangle
. Drawing aShape
can't be implemented atShape
class since different shapes have different formulas. The best option to handle scenario : leavedraw()
implementation to sub-classesoutput:
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 completedraw()
function.Now same example with Template method pattern:
output:
Once you have decided that you have to make as method
abstract
, you have two options : Either userinterface
orabstract
class. You can declare your methods ininterface
and defineabstract
class as class implementing theinterface
.