子类/继承标准容器? [英] Subclass/inherit standard containers?

查看:228
本文介绍了子类/继承标准容器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我经常在Stack Overflow上看到这个声明。就个人而言,我没有发现任何问题,除非我以多态方式使用它;即我必须使用 virtual 析构函数。



如果我想扩展/添加标准容器的功能那么比继承一个更好的方法是什么?将这些容器包装在自定义类中需要更多的努力,并且仍然不清洁。

解决方案



首先,这是一个坏主意,因为标准容器没有虚拟析构函数。您不应该使用不具有虚拟析构函数的多态,因为您不能保证派生类中的清除。



虚拟反思的基本规则



其次,设计真的很糟糕。实际上有几个原因是坏的设计。首先,您应该始终通过一般操作的算法来扩展标准容器的功能。这是一个简单的复杂性原因 - 如果您必须为每个适用于您的容器编写一个算法,并且您有M个容器和N个算法,那就是必须写入的M x N个方法。如果您一般编写算法,则只能使用N个算法。所以你得到更多的重用。



它也是非常糟糕的设计,因为你通过继承从容器打破了一个好的封装。一个很好的经验法则是:如果您可以使用类型的公共接口执行所需的内容,请在该类型的外部创建新行为。这改善了封装。如果这是您要实现的新行为,请将其命名为空间范围函数(如算法)。如果你有一个新的不变量来施加,在一个类中使用遏制。



封装的经典描述



最后,一般来说,您不应该将继承作为扩展类的行为的手段。这是早期OOP理论的一个大而不好的谎言之一,由于对重用的思考不清楚,它仍然被教导和推广到今天,即使有一个明确的理论为什么它不好。当您使用继承来扩展行为时,您将扩展行为与将接口用户联系到未来更改的方式绑定到您的接口合同。例如,说你有一个Socket类可以使用TCP协议进行通信,并且通过从Socket派生一个SSLSocket类来扩展它的行为,并在Socket之上实现更高的SSL栈协议的行为。现在,我们假设您有一个新的要求,具有相同的通信协议,但通过USB线路或通过电话。您需要将所有这些工作剪切并粘贴到从USB类或Telephony类派生的新类中。而现在,如果你发现一个错误,你必须在三个地方进行修复,这并不总是会发生,这意味着错误将花费更长时间,而不是总是被修复...



对于任何继承层次结构,这是一般的A-> B-> C - > ...当您想要使用在派生类中扩展的行为,如B,C,基础类A,您必须重新设计或重复实施。这导致非常单一的设计,很难改变道路(认为微软的MFC,或者他们的.NET,或者 - 他们使这个错误很多)。相反,您应该几乎总是通过构图来考虑扩展。当你想开/关原则时,应该使用继承。您应该通过继承类具有抽象基类和动态多态运行时,每个都将完全实现。层次结构不应该很深 - 几乎总是两个层次。当您有不同的动态类别时,只能使用两个以上的类型,以满足类型安全性需要区分的各种功能。在这些情况下,使用抽象基础直到叶子类,具有实现。


I often read this statements on Stack Overflow. Personally, I don't find any problem with this, unless I am using it in a polymorphic way; i.e. where I have to use virtual destructor.

If I want to extend/add the functionality of a standard container then what is a better way than inheriting one? Wrapping those container inside a custom class requires much more effort and is still unclean.

解决方案

There are a number of reasons why this a bad idea.

First, this is a bad idea because the standard containers do not have virtual destructors. You should never use something polymorphically that does not have virtual destructors, because you cannot guarantee cleanup in your derived class.

Basic rules for virtual dtors

Second, it is really bad design. And there are actually several reasons it is bad design. First, you should always extend the functionality of standard containers through algorithms that operate generically. This is a simple complexity reason - if you have to write an algorithm for every container it applies to and you have M containers and N algorithms, that is M x N methods you must write. If you write your algorithms generically, you have N algorithms only. So you get much more reuse.

It is also really bad design because you are breaking a good encapsulation by inheriting from the container. A good rule of thumb is: if you can perform what you need using the public interface of a type, make that new behavior external to the type. This improves encapsulation. If it's a new behavior you want to implement, make it a namespace scope function (like the algorithms). If you have a new invariant to impose, use containment in a class.

A classic description of encapsulation

Finally, in general, you should never think about inheritance as a means to extend the behavior of a class. This is one of the big, bad lies of early OOP theory that came about due to unclear thinking about reuse, and it continues to be taught and promoted to this day even though there is a clear theory why it is bad. When you use inheritance to extend behavior, you are tying that extended behavior to your interface contract in a way that ties users hands to future changes. For instance, say you have a class of type Socket that communicates using the TCP protocol and you extend it's behavior by deriving a class SSLSocket from Socket and implementing the behavior of the higher SSL stack protocol on top of Socket. Now, let's say you get a new requirement to have the same protocol of communications, but over a USB line, or over telephony. You would need to cut and paste all that work to a new class that derives from a USB class, or a Telephony class. And now, if you find a bug, you have to fix it in all three places, which won't always happen, which means bugs will take longer and not always get fixed...

This is general to any inheritance hierarchy A->B->C->... When you want to use the behaviors you've extended in derived classes, like B, C, .. on objects not of the base class A, you've got to redesign or you are duplicating implementation. This leads to very monolithic designs that are very hard to change down the road (think Microsoft's MFC, or their .NET, or - well, they make this mistake a lot). Instead, you should almost always think of extension through composition whenever possible. Inheritance should be used when you are thinking "Open / Closed Principle". You should have abstract base classes and dynamic polymorphism runtime through inherited class, each will full implementations. Hierarchies shouldn't be deep - almost always two levels. Only use more than two when you have different dynamic categories that go to a variety of functions that need that distinction for type safety. In those cases, use abstract bases until the leaf classes, which have the implementation.

这篇关于子类/继承标准容器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆