组合:使用特征来避免转发功能?

Posted

技术标签:

【中文标题】组合:使用特征来避免转发功能?【英文标题】:Composition: using traits to avoid forwarding functions? 【发布时间】:2014-01-22 02:01:16 【问题描述】:

假设我们有两个类,AB。当使用组合来建模 "has-a""is-implemented-in-terms-of" 关系时(例如 B has-a A),与继承相比的缺点之一是 B 不包含它需要的 A 的公共功能。为了访问As 的公共函数,必须提供转发函数(与继承相反,B 将继承As 的所有公共函数)。

举一个更具体的例子,假设我们有一个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,建议如下:

使用组合代替继承的一个缺点是所有 由组合类提供的方法必须是 在派生类中实现,即使它们只是转发 方法。 [...] 这个缺点可以通过使用特征来避免。

我对特质的概念还很陌生,而且根据我所阅读的所有内容,我发现很难与上述陈述相关联。因此,我的问题是:如何使用特征来避免通过组合转发函数?基于我的示例(PersonContactInfo)的答案将是理想的。

编辑:为了澄清,为了回应一些答案,我知道私有继承是建模的替代组合"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)

【讨论】:

以上是关于组合:使用特征来避免转发功能?的主要内容,如果未能解决你的问题,请参考以下文章

需要帮助,VBA,需要组合框来避免输入特定值

避免将状态选择上下文向下传递到调用堆栈的“功能方式”是啥?

Hbase 表的Rowkey设计避免数据热点

Linux 用户应该使用哪种 IDE/编译器组合在 Windows 上构建 Qt 应用程序,同时避免使用 MS Visual Studio?

在PhantomJS上运行测试时避免使用键盘组合

避免来自底部导航快速点击的双重 API 调用