条款38:通过复合塑模出has-a或“根据某物实现出”

1.前言

复合是类型之间的一种关系,当某种类型的对象内含它种类型的对象,便是这种关系。比如以下例子:

class Address{...};//某人的住址
class PhoneNumber{....};
class Person{

    public:
        ...
    private:
        std::string name;
        Address address;
        PhoneNumber voiceNumber;
        PhoneNumber faxNumber;

};

在本例中,Person对象由string ,Address,PhoneNumber构成。复合这个术语有多种意思:containment(内含),aggregation(聚合),embedding(内嵌)。

2.实例分析

复合实际上有两个意义,复合意味着has-a(有一个)或(is-implemented-terms-of)(根据某物实现出)。程序中的对象其实相当于你所塑造的世界中的某些事物,比如人,汽车,一张张视频画面等等,这些对象都属于应用域部分。其它对象则是纯粹为了实现细节的人工制品,像是缓冲区,互斥器,查找树等,这些对象相当于你的软件实现域。当复合发生在应用域的对象之间,表现出has-a的关系;当它发生于实现域内则是表现is-implemented-in-terms-of的关系

上述的Person class示范has-a关系。Person有一个名称,一个地址,以及语音和传真两笔电话号码。大多数人对区分is-a和has-a感到困惑。

比较麻烦的是区分is-a(是一种)和is-implemented-in-terms-of(根据某物实现出)这两种关系。假设你需要一个template,希望编写一个classes用来表现出不重复对象组成的sets。由于复用是件很美妙的事情,第一直觉是采用标准程序提供的set template。

然而set的实现往往会导致“每个元素耗用三个指针”的额外开销。因为sets通常以平衡查找树(balanced search trees)实现完成,使得它们在查找,安插,移除元素时保证拥有对数时间的效率。当速度比空间重要时,这是个合理的设计。但是当空间比速度重要的情况下,此时那个设计似乎显得不合理。

实现sets的方法有很多,其中一种就是在底层采用linked lists,而你又刚好知道,标准程序库有一个list template,于是决定采用它。

更明确地说,你决定让你那个萌芽中地set template继承std::list。也就是让Set<T>继承list<T>。毕竟在你的实现理念中Set对象其实是个list对象。于是设计了Set template如下:

template<typename T>  //将list应用于Set.错误做法
class Set:public std::list<T> {...};

没件事看上去都很好,但实际上有些东西是完全错误地,如条款32所说:如果D是一种B,对B为真地每一件事也都应该为真。但List可以内含重复元素,如果数值3052被插入到list<int>两次,那么List将内含两笔3051.Set不可以内含重复地元素,如果数值3051被插入Set<int>两次,这个set只内含一笔3051,因此set是一种list并不为真。

由于这两个classes之间并非is-a的关系,所以public继承并不适合用来塑模它们。正确的做法是应该了解set对象可根据一个list对象实现出来:

template<class T>
class Set{

    public:
        bool member(const T& item) const;
        void insert(const T& item);
        void remove(const T& item);
        std::size_t_size() const;
    private:
        std::list<T> rep;//用来表述Set的数据
};

Set的成员函数可以大量依赖list及标准程序库提供的其它部分机能来完成,所以实现很直观也很简单,只要你熟悉以STL编写程序:

template<typename T>
bool Set<T>::member(const T& item) const
{

    return std::find(rep.begin(),rep.end(),item)!=rep.end();
}
template<typename T>
void Set<T>::insert(const T& item)
{
    if(!member(item)) rep.push_back(item);
}
template<typename T>
void Set<T>::remove(const T& item)
{
    typename std::list<T>::iterator it=std::find(rep.begin(),rep.end(),item);
    if(it!=rep.end()) rep.erase(it);
}
template<typename T>
std::size_tSet<T>::size() const
{
    return rep.size();
}

这些函数如此简单,因为都适合声明为inline。

3.总结

(1)复合的意义和public继承完全不同

(2)在应用领域(application domain),复合意味着has-a(有一个)。在实现域,复合意味着is-implemented-in-terms-of(根据某物实现出)

相关推荐

  1. 25:考虑写一个不抛异常的swap函数

    2024-01-11 08:28:01       53 阅读
  2. 34:区分接口继承和实现

    2024-01-11 08:28:01       54 阅读
  3. 《Effective C++》33

    2024-01-11 08:28:01       59 阅读
  4. 《Effective C++》37

    2024-01-11 08:28:01       57 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-11 08:28:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-11 08:28:01       101 阅读
  3. 在Django里面运行非项目文件

    2024-01-11 08:28:01       82 阅读
  4. Python语言-面向对象

    2024-01-11 08:28:01       91 阅读

热门阅读

  1. spark相关

    2024-01-11 08:28:01       64 阅读
  2. 初学者的嵌入式 Linux 计划!

    2024-01-11 08:28:01       57 阅读
  3. 第七讲_css浮动

    2024-01-11 08:28:01       59 阅读
  4. Windows Copilot 更新及使用教程

    2024-01-11 08:28:01       177 阅读
  5. onlyOffice实践-在线协同word、ppt、excel编辑

    2024-01-11 08:28:01       139 阅读
  6. Go 企业级gRPC原理

    2024-01-11 08:28:01       50 阅读
  7. Vue3中的`ref`和`reactive使用中遇到的一些坑

    2024-01-11 08:28:01       56 阅读