组合:使用特征来避免转发功能?
Posted
技术标签:
【中文标题】组合:使用特征来避免转发功能?【英文标题】:Composition: using traits to avoid forwarding functions? 【发布时间】:2014-01-22 02:01:16 【问题描述】:假设我们有两个类,A
和 B
。当使用组合来建模 "has-a" 或 "is-implemented-in-terms-of" 关系时(例如 B
has-a A
),与继承相比的缺点之一是 B
不包含它需要的 A
的公共功能。为了访问A
s 的公共函数,必须提供转发函数(与继承相反,B
将继承A
s 的所有公共函数)。
举一个更具体的例子,假设我们有一个Person
有一个 ContactInfo
:
using namespace std;
class ContactInfo
public:
ContactInfo();
void updateAddress(string address);
void updatePhone(string phone);
void updateEmail(string email);
private:
string address;
string phone;
string email;
;
class Person
public:
Person();
// Forwarding functions:
void updateAddress(string address)contactInfo.updateAddress(address);
void updatePhone(string phone)contactInfo.updatePhone(phone);
void updateEmail(string email)contactInfo.updateEmail(email);
private:
ContactInfo contactInfo;
;
忽略设计中的任何缺陷(这只是一个人为的例子来证明我的问题),我不得不在Person
中繁琐地复制来自ContactInfo
的确切函数签名。在一个更复杂的例子中,可能会有许多这样的函数,以及许多层的组合类,这会导致大量代码重复,以及所有常见的维护问题和容易出错等。
尽管如此,这是根据条款 38 等来源对 “has-a” 或 “is-implemented-in-terms-of” 建模的推荐做法Meyers 的 Effective C++ 和 Sutter 的 Exceptional C++ (link) 的第 24 条。
在研究这个问题时,我遇到了this Wikipedia article,它讨论了相同的主题。 At the bottom of the article,建议如下:
使用组合代替继承的一个缺点是所有 由组合类提供的方法必须是 在派生类中实现,即使它们只是转发 方法。 [...] 这个缺点可以通过使用特征来避免。
我对特质的概念还很陌生,而且根据我所阅读的所有内容,我发现很难与上述陈述相关联。因此,我的问题是:如何使用特征来避免通过组合转发函数?基于我的示例(Person
和 ContactInfo
)的答案将是理想的。
编辑:为了澄清,为了回应一些答案,我知道私有继承是建模的替代组合"is-implemented-in-terms-of "。我的问题不是关于那个,而是关于***关于特征的声明的含义。我不是在要求作曲的替代品。我加粗了我的问题,以便更清楚地表明这就是我要问的。
【问题讨论】:
我认为***在这方面误导了你。 Traits 是泛型编程的工具,比如说你想包含一些模板类型参数类型的contactInfo
。
“有”关系是“有”关系。没有“人为的例子”,因为在任何情况下,都可以解决决定某事是某事还是有某事的问题。如果您无法解决此问题,那么您在其他地方犯了错误。一个棘手的例子是这样的:汽车是交通工具,飞机是交通工具,那么火车也是交通工具?答案是不。火车是一组车辆(前置发动机、货车,也可以移动,也可以选择后置车辆)。你总是必须做出明确的区分,否则你会遇到麻烦。
我觉得这篇***文章不好:-/
@lukasz1985 谢谢,但我的问题不是关于如何区分“has a”和“is a”,而是关于特征。
我认为您不应该使用特质来更轻松地完成工作。您可能弄乱了组合,因为在您的示例中,您应该能够通过使用包含类(如 Person->getContactInfo() )来获取底层对象。好处是不要先使用特征和多重继承。如果这样下去,唯一剩下的就是在继承和组合(IS A 或 HAS A)之间做出决定。您可以在这里查看我的答案:***.com/questions/49002/…
【参考方案1】:
也许你可以尝试私有继承:
class Person : private ContactInfo
public:
Person()
using ContactInfo::updateAddress;
using ContactInfo::updatePhone;
using ContactInfo::updateEmail;
;
int main()
Person person;
person.updateAddress("hi");
return 0;
尽管您可能需要注意此FAQ 中列出的注意事项:
还有几个区别:
如果您希望每辆车包含多个引擎,则需要简单组合变体 私有继承变体可能引入不必要的多重继承 私有继承变体允许 Car 的成员将 Car* 转换为 Engine* 私有继承变体允许访问基类的受保护成员 私有继承变体允许 Car 覆盖 Engine 的虚函数 私有继承变体使得给 Car 一个 start() 方法稍微简单一些(20 个字符与 28 个字符相比) 简单地调用引擎的 start() 方法
否则所提供的组合示例似乎与您的相同。点击wikipedia文章中的traits链接并没有提供任何C++文章,参考中的链接似乎是关于type traits
。我找不到 type traits
与您的场景有什么关系。
【讨论】:
【参考方案2】:文章用Interface谈继承,
所以事实上它告诉对象必须尊重一些签名。
类型特征可用于检查签名是否正确并分派给适当的函数
例如一些STL算法等待类型Iterator,
但这些迭代器不是从class Iterator
继承的,而是必须提供一些契约(operator ++()
、operator !=(rhs)
、operator*()
)。
以文章为例:
类玩家 - 可以移动 类建筑 - 不能移动和代码:
#if 1
// simple type traits which tells if class has method update_position
template <typename T> struct can_move;
// Hardcode the values
template <> struct can_move<Player> static const bool value = true; ;
template <> struct can_move<Building> static const bool value = false; ;
#else
// or even better, but need a has_update_position. (see how to check if member exist in a class)
template <typename T> struct can_move static const bool value = has_update_position<T>::value ;
#endif
template <typename T, bool> struct position_updater;
// specialization for object which can move
template <typename T> struct position_updater<T, true>
static void update(T& object) object.update_position();
;
// specialization for object which can NOT move
template <typename T> struct position_updater<T, false>
static void update(T& object) /* Do nothing, it can not move */
;
template <typename T>
void update_position(T& object)
// statically dispatch to the correct method
// No need of interface or inheritance
position_updater<T, can_move<T>::value>::update(object);
【讨论】:
【参考方案3】:首先我应该提到,特征在 C++/STL 和 php、Lasso 等语言中是不同的东西。看起来***的文章指的是类似 PHP 的特征,因为 C++/STL 特征不是为多态重用而设计的(我们正在谈论具有多态行为的代码重用,对吗?)。它们旨在简化模板类的声明。
特征用于一些不支持多重继承的语言(PHP、Lasso 等)。 Traits 允许“模拟”多重继承(但多重继承和 Traits 并不完全相同)。
相比之下,C++ 不支持 PHP 特征,但支持多重继承。因此,如果谈到 C++,那么 trait-like 解决方案将是这样的:
class VisibleTrait
public:
virtual void draw();
;
class SolidTrait
public:
virtual void collide(Object objects[]);
;
class MovableTrait
public:
virtual void update();
;
// A player is visible, movable, and solid
class Player : public VisibleTrait, MovableTrait, SolidTrait
;
// Smoke is visible and movable but not solid
class Smoke : public VisibleTrait, MovableTrait
;
// A hause is visible and solid but not movable
class House : public VisibleTrait, SolidTrait
;
所以回答你的问题
如何使用特征来避免转发函数 作品?
1) 特征不会避免使用组合转发函数,因为特征独立于组合工作。 (来自***的文章在特征和组成之间的关系方面有点误导) 2) 类似 PHP/Lasso 的特征可以在具有多重继承的 C++ 中部分模拟。
【讨论】:
感谢您提供直接解决我问题的答案。 @JBentley,可以使用 c++ 中的特征,以便通过组合提供更好的方法转发。在此处查看我的相关 SO 问题:***.com/questions/28393195/…【参考方案4】:AFAIK 特征类类似于以下内容:
#include <iostream>
using namespace std;
class ContactInfo
public:
void updateAddress() cout << "update address"; ;
void updatePhone() ;
void updateEmail() ;
;
template<class T> class TraitClass
public:
private:
T obj;
;
template<> class TraitClass<ContactInfo>
public:
void updateAddress() obj.updateAddress();;
void updatePhone() obj.updatePhone();;
void updateEmail() obj.updateEmail();;
private:
ContactInfo obj;
;
class Person
public:
void updateAddress() obj.updateAddress();;
void updatePhone() obj.updatePhone();;
void updateEmail() obj.updateEmail();;
private:
TraitClass<ContactInfo> obj;
;
int main()
Person myPerson;
myPerson.updateAddress();
return 0;
即:一个编译时模板类,您可以在其中实现(和/或专门化)您的委托方法,以便转发您需要的任何内容,而无需(无论出于何种原因)重复继承。
http://en.wikipedia.org/wiki/Trait_(computer_programming)
【讨论】:
以上是关于组合:使用特征来避免转发功能?的主要内容,如果未能解决你的问题,请参考以下文章
Linux 用户应该使用哪种 IDE/编译器组合在 Windows 上构建 Qt 应用程序,同时避免使用 MS Visual Studio?