目录 Table of Contents
13 委托相关设计模式
Delegation (委托) + Inheritance (继承)
Composite 组合
现在我们面对的问题是写一个文件系统, 我们怎么来思考这个问题呢 ?
一想文件系统, 首先想到的是里面有文件有目录, 目录里面可以放文件, 目录里面还可以放其它的目录等等
这是非常经典的一种状况. 我需要写出一个代表 file 的 class, 这里我把它叫作 Primitive
. 另外我应该准备一个 class 叫作 Composite
, 它应该是一个容器, 因为它应该可以容纳很多个 primitive 这种东西, 并且它还应该可以容纳和它同样的这种东西, 也就是一个 composite 可以容纳另外的 composite.
当我们要选择 C++ 的容器来用的时候, 我们要声明它. 右边这个容器我们要放什么东西, 我也不写死写成左边, 也不写死写成右边, 我说我要放的是上面这种东西, Component
父类. 而且放的是指针, 因为在 C++ 里面只允许放指针, 在容器里面放的东西一定要大小相等, 指针一定是一样的大小
然后右边这种东西, 它是一个复合物聚合物, 所以它应该有一个函数叫作 add()
, 用来往里面加东西. 然后参数, 因为它要可以加左边这种东西, 又要可以加右边这种东西, 所以我们让它的参数是一个指针, 指针指向父类, 那么这样就可以左右都接受了
这样的一个结构, 我们把它叫作 Composite, 组合模式
至于这里面除了把元素加进去之外, 这个 composite 还应该具备那些功能呢 ? 就说这里的文件系统肯定还需要一些其它的功能, 但是这不是这里要讨论的东西.
首先右边的 component. 这个 add()
函数应该要被子类重新定义, 所以它是一个虚函数. 如果你把它设计为纯虚函数, 那就不行了, 因为设计为纯虚函数的话, 就是你没有默认定义了, 子类就一定要去重新定义它, 而这里的 Primitive 子类并不需要重新定义它. 像图上这样写就行了, 写成一个空函数
然后右下角的 composite. 我们把刚才分析的结果写成代码.
Prototype 原型
题目 : 我需要一个继承体系, 柱状继承体系. 我想要去创建未来才会出现的子类应该怎么办.
这个 "我" 在图里表现出来就是这个波浪线, 上面的就是抽象的部分. 就好像我现在在设计一个框架, 这些子类是被客户买去之后才会派生子类, 这个时候 class 名称才出现. 我可能是几年前写的这个代码, 我不知道未来的 class 名字是什么, 那我怎么去创建它呢 ?
有的大佬就想到了一种办法, 可不可以下面的这些子类, 你们都创建一个你们自己, 当成原型 prototype. 让这个我, 也就是框架里面, 有办法看到你们创造出来的这些原型放到什么位置上, 这样我就可以复制它, 然后就等于我在创建了.
这个听起来可能很抽象.
我们怎么去解决这个问题呢. 刚刚提到的一个想法, 让子类自己去创造自己.
我们从 LandSatImage 开始. 我在这一个 class 里面安排一个静态的对象, 因为图上有下划线, 下划线表示静态. 它的数据类型就是 LandSatImage.
画图的时候和写代码是相反的, 画图的时候是先写对象的名称, 然后写对象的类型
所以每一个子类都写成这样的话, 它们就自己创造了自己, 这就是原型. 而这个原型必须被登记到波浪线上面的部分去, 让框架能够看得到.
怎么办呢 ? 应该有由上面的框架准备一个空间, 你下面的就把原型放上去, 放上去之后, 上面的空间里面就有了子类, 上面就可以看到了
这个静态的自己创建出来之后, 会调用它的构造函数 LandSatImage()
, 这个构造函数我把它写成 private.
这里前面加了一个符号
-
, 所以说它是私有的, 当然正的+
代表 public,#
代表 protected
我们把构造函数设为私有的, 那么刚才子类创建自己的时候私有的构造函数能够被调用吗 ? 答案是可以, 因为是自己, 所以是自己人在调用构造函数.
我们就用这个构造函数来调用 addPrototype(this);
, 把自己 add 上去, 这个函数是父类写的, 就是把得到的指针, 放到父类上面的容器里面去, 这里容器是数组. 下面创建出来的原型要放到上面去, 这样上面才看得到.
同理右边也这么做, 其它所有的子类都这么做.
然后这些子类都应该自己准备一个 clone()
函数. 这个函数就是 new
一个自己. 刚刚已经有一个原型了, 所以上面的框架端就可以通过原型调用 clone()
函数, 这样就做出了一个副本出来. 没有这个原型, 就没有办法通过对象调用 clone()
那这样如果我不要原型, 我让这个 clone()
是一个静态函数, 那不也是可以调用到吗. 想一想, 静态函数的调用, 一定要 class name, 这里没有 class name, 所以这个想法不行.
现在每一个子类, 自己有自己的一个个体, 并且有一个构造函数把自己这个个体挂到上面去, 然后每一个子类有一个 clone, 让框架端能够看到挂上去的这个自己, 一个一个的原型, 得以通过原型调用这个 clone, 制造出副本出来
相关书籍 : Design Patterns Explained Simply
那么这样, 下面的这些子类是不是有了一种开销, 本来是我自由自在地派生下去写东西, 现在你告诉我说我必须要有一个静态的自己, 必须写一个私有的构造函数, 必须写一个 clone 函数, 这样一种负担, 这合理吗.
当然合理, 因为你要搭配上面这种写好的框架, 你一定要付出额外的开销
不太熟悉画图的形式的话, 我们还是看一遍源代码
父类 Image 里面有一些静态函数
第 13 行有一个 clone 函数, 它是纯虚函数
第 21 行, 有一个容器存放所有的原型, 为了示范这里只是一个大小为 10 的数组. 不要忘了, class 里面一个静态的数据, 一定要在类的外面做一次定义. 也就是 第 24 和第 25 行.
右边这个函数, 父类的这个成员函数 findAndClone
, 就是当下面所有的子类把自己的一份原型放上去之后, 填充了这一个数组, 填好了之后, 这个函数让框架端去找适当的. 这里是用数组下标来找, 现实中可能会用 class name 来找. 找出来的那一个, 调用 clone
就做了一个副本出来.
然后我们看子类, 两个子类, 左右各一个
我们看左边
子类继承父类, 里面先把 clone 函数找出来, 第 11 行, 它的作用是 new 自己, 这里面的细节等会再说
第 22 行, 我有一个静态的变量, 叫这个名字, 它是自己这个类型 (在上面的图里面这里叫 LSAT, 是缩写). 静态的自己创建出来后, 会调用构造函数, 就是第 24 行. 构造函数放在私有区域里面, 但是没关系, 自己人调用是 OK 的. 然后构造函数作用是把自己放到框架那一端去
现在最后一步来了, 我们回到最开始的图那里, 图上画了两个构造函数, 其中有一个是 protected, 这有什么特殊用意吗 ?
刚刚看到已经把静态的自己放上去了, 上面就等待着 findAndClone, 当有需要的时候就去找并且将其 clone, clone 的时候做的动作是 new 自己, new 自己也要调用构造函数. 这次也可以调用到 private 的那个构造函数, 但是这次我们不能让它调用 private 的那一个了, 因为 private 这一个又把自己放上去, 上面的容器是用来放原型的, 就是只能放原来的那一个, 并不能放新出来的这一个.
怎么办呢, 我们再写一个构造函数, 让现在这个 new 去调用, 这个第二个构造函数其实 private 或者 protected 都可以, 只要能够和上面的区分开来, 这个例子用的是 protected. 这个构造函数加了一个参数用以区分开, 当然这个参数根本用不到. 对应第 14 行代码.