继承与聚合以及“has-a”与“is-a”相对。
在我的代码中,我发现使用类似 mixin 的继承来组合具有不同块的对象很有用。我有:
class Name
{
public:
typedef int32_t value_type;
public:
// ctors and dtors
void set_value(value_type value) { value_ = value; }
const value_type& value() const { return value_; }
private:
value_type value_;
};
class NamedObject
{
public:
void set_name(const Name& name) { name_ = name; }
const Name& name() const { return name_; }
protected:
// ctors and dtors
private:
Name name_;
};
并且我使用这种基类来为对象提供具有预定义非虚拟功能的属性:
class MyObject: public NamedObject, public HasZlevel {
// functionality that is not connected with NamedObject and HasZLevel
};
因此,我决定将 MyObject 视为“is-a”NamedObject,而不是“has-a”Name。 Z-level 和 Name 是在 MyObject 实例的生命周期内永远不会改变的属性。与聚合相比,我更喜欢这种方法,因为在仅为 NamedObjects 定义的算法中简化了使用,或者对于具有 HasZLevel 接口的对象,我可以通过 NamedObject* 或 HasZLevel* 传递它们,并确保它们不会因受保护而被删除或附加博士和演员。
另一个选项是聚合:
class MyObject
{
public:
MyObject(const Name& name): name_(name) {}
void set_name(const Name& name) { named_->set_name(name) }
// and so on...
private:
Name name_;
};
要使用它,我需要模板算法,该算法要求参数类型具有 set_name 成员函数。
在我的例子中,有什么充分的理由放弃类似 mixin 的设计并使用聚合吗?也许是为了长期维护和改装?
In my code I find it useful to use mixin-like inheritance to compose objects with different blocks. I have:
class Name
{
public:
typedef int32_t value_type;
public:
// ctors and dtors
void set_value(value_type value) { value_ = value; }
const value_type& value() const { return value_; }
private:
value_type value_;
};
class NamedObject
{
public:
void set_name(const Name& name) { name_ = name; }
const Name& name() const { return name_; }
protected:
// ctors and dtors
private:
Name name_;
};
And I use this kind of base classes to provide objects with properties with pre-defined non-virtual functionality:
class MyObject: public NamedObject, public HasZlevel {
// functionality that is not connected with NamedObject and HasZLevel
};
So I decide to treat MyObject as "is-a" NamedObject instead of "has-a" Name.
Z-level and Name are properties that will never change during MyObject instance's lifetime. I prefer this to aggregation because of simplified usage in algorithms that are defined just for NamedObjects, or for objects that have HasZLevel interface, i can pass them via NamedObject* or HasZLevel* and be sure that they will not be deleted or appended due to protected dtors and ctors.
Another option is aggregation:
class MyObject
{
public:
MyObject(const Name& name): name_(name) {}
void set_name(const Name& name) { named_->set_name(name) }
// and so on...
private:
Name name_;
};
To use this I need template algorithm that requires from parameter type to have set_name member function.
Are there any good reasons in my case to abandon my mixin-like design and to use aggregation? Maybe in long-term maintenance and modification?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
因为多重继承很糟糕。
简而言之:这真的会帮助您更快地交付代码吗?或者,当某些重写的成员函数阻塞继承树上的其他成员时,您将在调试器中调试多重继承的类并单步执行超类的棘手问题,这会花费更多的时间吗?
是的,我确实写了“让会员窒息”。多重继承确实有那么糟糕。
Because Multiple Inheritance Sucks.
The long and short of it is: will this actually help you ship your code faster? Or will it cost more time than it's worth because you'll be debugging multiply inherited classes and stepping through a gordian knot of superclasses in a debugger when some overridden member function chokes on some other member up the inheritance tree?
And yes, I did just write 'chokes on a member'. Multiple inheritance really is that bad.
经验法则是“尽可能使用最弱的关系”。看看:http://www.gotw.ca/publications/mill06.htm 和 http://www.gotw.ca/publications/mill07.htm
您可以使用私有继承来建模“is-implemented-in-term-of”,但您应该考虑异常安全含义 http://www.gotw.ca/gotw/060.htm
我知道,我喜欢本周的大师。 ;-)
编辑
为什么聚合比继承更好:
1)您可以通过继承隐藏方法(或更改名称),默认情况下所有方法都可用。
2) 您可以使用带有非虚拟析构函数的类(std::string 和所有 stl 容器)。
3)您可以轻松更改实现,如果您想更改底层对象,您的接口将保持不变,但是如果您从不同的基类继承,您的接口可能会更改。
4)异常安全(参见第三个链接)。
5)易于阅读。
当需要“私有继承”时:
1)当您要使用的类是抽象类时(不能使用聚合)。
2)当你想使用受保护的方法时。
当需要“公共继承”时:
1)当您想要访问私有方法和数据时(您不应该这样做)。
最后,聚合可以让您更好地控制界面。我通常更喜欢这个选项。私有继承有时是唯一的选择,但代价是接口更加严格,并且还会影响异常安全。我尽量避免使用私有继承。
我从未使用公共继承来建模“has-a”。
The rule of thumb is "use the weakest relation you can". Have a look at: http://www.gotw.ca/publications/mill06.htm and http://www.gotw.ca/publications/mill07.htm
You can use the private inheritance to model "is-implemented-in-term-of" but you should think at the exception safe implication http://www.gotw.ca/gotw/060.htm
I know, I love guru of the week. ;-)
EDIT
Why aggregation is better then inheritance:
1) You can hide methods (or change names) with inheritance all the methods are available by default.
2) You can use class with no-virtual destructor (std::string and all stl containers).
3) You can change implementation easily, if you want change the underlying object your interface will be untouched, however if you inherit from a different base class your interface could change.
4) Exception safe (see the third link).
5) Easy to read.
When "private inheritance" is needed:
1) When the class than you want to use is abstract (you cannot use aggregation).
2) When you want to use protected methods.
When "public inheritance" is needed:
1) When you want to access to private methods and data (you shouldn't do that).
In the end, aggregation allows you a better control over your interface. I usually prefer this option. Private inheritance is sometimes the only option, however the cost is a more rigid interface and also consequences on exception safety. I try to avoid using private inheritance as much as possible.
I haven't ever used public inheritance to model "has-a".
问题是,如果您像在这里所做的那样公开继承,那么有时,某个地方,有人会将您的类视为
NamedObject
或HasZlevel
并尝试由父级删除指针、传递它等等。这可能不会太好。如果你想使用这样的 mixin 类型策略,至少使用
private
继承,然后using
你需要的父方法到各个子类的公共部分( es)。但我并不特别建议这样做。如果您使用继承来表示替换,那么您的基类表示一个接口,而不是功能。我想说在这种情况下使用组合更有意义。整个标准库都是基于通用(模板)算法的,所以我不会处理这个问题。独立的模板算法方法是扩展代码的好方法,而无需修改类内部结构,甚至其公共接口。
The thing is, if you publicly inherit like you're doing here, sometime, somewhere, someone will treat your class as a
NamedObject
or aHasZlevel
and try to delete by parent pointer, pass it around, etc. And this will probably just not work out too well.If you want to use a mixin type strategy like this, at least use
private
inheritance, and thenusing
the parent methods you need into the public sections of the respective child class(es).However I wouldn't particularly suggest doing even that. If you use inheritance to represent substitution then your base classes represent an interface, not features. I would say that using composition in this case makes more sense. The entire standard library is based around generic (template) algorithms so I wouldn't work about that. Free-standing template algorithm methods are a great way to extend your code without having to modify your class internals, or even its public interface.
多重继承的主要问题是,有人会尝试将一个人建模为从“手臂”和“腿”继承,然后抱怨多重继承混淆了问题。
Mixins 来自 Lisp 世界,我从来不相信它们那么适合 C++。然而,多重继承本身并不是邪恶的,而且可能是必要的。某事物与多个事物具有“是”关系是很常见的。
根据经验,我尝试仅使用空基类,即 Java 所谓的接口。如果我在基类中需要任何类型的数据,我几乎总是发现我的设计与尝试将人员类定义为继承自“arm”和“leg”一样糟糕。也有例外,如果您相信自己已经找到了例外,请尝试编写一份文档并请一些朋友对其发表评论。经验法则并不是正式的数学证明,但它对我来说非常有用。
The main problem with multiple inheritance is that somebody will try to model a person as inheriting from "arm" and "leg" and then complain that multiple inheritance confused the issue.
Mixins come from the Lisp world, and I've never been convinced that they fit C++ all that well. However, multiple inheritance itself isn't evil, and can be necessary. It is very common for something to have an "is-a" relationship with more than one thing.
As a rule of thumb, I try to use only empty base classes, i.e., what Java calls interfaces. If I need data of any kind in my base classes I have almost always found that my design was as bad as trying to define a person class as inheriting from "arm" and "leg." There are exceptions, and if you believe you've found one, try writing up a document and asking a few friends to comment on it. A rule of thumb isn't a formal mathematical proof, but it has been very useful to me.
正如几乎所有人所说,那些多重继承对象是可怕的、可怕的和无用的。
我认为您问自己错误的问题,或者,我如何确保我的代码可以在编译时进行静态检查。
您应该问的第一个问题是:我需要在实施决策中隐藏哪些细节。
你在哪里使用这些悲伤的物品?他们在建模什么?
As almost everyone said those multi inheritance objects are scary, horrible and useless.
I think you are asking yourself the wrong question or, how do I make sure that my code can be statically checked at compile time.
The first question you should ask is: What details do I need to hide from my implementation decision.
Where are you using these sad objects? What are they modeling?
有一种方法可以通过将我的使用模式转换为以下方式来避免多重继承:
因此,我可以以某种方式创建所需功能的对象
,或者
是否可以使用默认构造函数来防止编译器日志污染。
我看到的主要优点 - 我可以划分不同属性的功能并为许多类使用划分的块。此外,我做了一些类似的概念,这意味着我明确定义了我在模板函数中使用的对象。
我看到一个从具体类继承的设计漏洞,但这需要解释它实际上如何威胁我的代码。
另一个问题是隐藏 Foo 类的 typedef(如果它是模板),并且我看不出有什么方法可以解决它。
这段代码闻起来很糟糕......
我可以以相同的方式声明 NamedCompare 和 NamedSet,但没有命名使用,只需从 T 调用 name() 即可。这将起作用,我可以在独立算法中使用它。我认为它比这些 mixin 好得多。
There is a way to avoid multiple inheritance by transforming my usage pattern to the following:
So I can make objects of wanted functionality in a way
or
if I can use default constructor to prevent pollution of compiler logs.
The main advantage that I see - I can divide functionality on different properties and use divided blocks for many classes. Additionally I make somewhat like concepts meaning I explicitly define with what objects I am working in a template functions.
I see one design hole that I inherit from concrete classes, but this is to be explained how it threatens my code actually.
Another problem is hiding of typedefs of Foo class if it is template and I can see no ways to work it around.
This code smells bad...
I can declate NamedCompare and NamedSet just in the same way but without Named usage, just call name() from T. This will work and I can use it in standalone algorithms. I think it's much better than these mixins.