diff --git "a/174.\347\262\276\350\257\273\343\200\212\350\256\276\350\256\241\346\250\241\345\274\217 - Composite \347\273\204\345\220\210\346\250\241\345\274\217\343\200\213.md" "b/174.\347\262\276\350\257\273\343\200\212\350\256\276\350\256\241\346\250\241\345\274\217 - Composite \347\273\204\345\220\210\346\250\241\345\274\217\343\200\213.md" new file mode 100644 index 00000000..5f7aef4b --- /dev/null +++ "b/174.\347\262\276\350\257\273\343\200\212\350\256\276\350\256\241\346\250\241\345\274\217 - Composite \347\273\204\345\220\210\346\250\241\345\274\217\343\200\213.md" @@ -0,0 +1,111 @@ +# Composite(组合模式) + +Composite(组合模式)属于结构型模式,是一种统一管理树形结构的抽象方式。 + +**意图:将对象组合成树形结构以表示 “部分 - 整体” 的层次结构。Composite 使得用户对单个对象和组合对象的使用具有一致性。** + +## 举例子 + +如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。 + +### 公司组织关系树 + +公司组织关系可能分为部门与人,其中人属于部门,有的人有下属,有的人没有下属。如果我们统一将部门、人抽象为组织节点,就可以方便的统计某个部门下有多少人、财务数据等等,而不用关心当前节点是部门还是人。 + +### 操作系统的文件夹与文件 + +操作系统的文件夹与文件也是典型的树状结构,为了方便递归出文件夹内文件数量或者文件总大小,我们最好设计的时候就将文件夹与文件抽象为文件,这样每个节点都拥有相同的方法添加、删除、查找子元素,而不需要关心当前节点是文件夹或是文件。 + +### 搭建平台的组件与容器 + +容器与组件的关系很小,用户常常认为容器也是一种组件,但搭建平台实现时,容器与组件稍有不同,不同之处在于容器可以嵌套子元素,而组件不可以。如果因此搭建平台就将组件分为容器与组件,会导致 API 割裂为两套,不利于组件开发者维护与用户理解,比较好的设计思路是将组件与容器统一看成组件,组件只是一种没有子元素的特殊容器,这样组件与容器就可以拥有相同的 API,统一理解与操作了。 + +## 意图解释 + +**意图:将对象组合成树形结构以表示 “部分 - 整体” 的层次结构。Composite 使得用户对单个对象和组合对象的使用具有一致性。** + +比较好理解,组合是指多个对象虽然有一定差异,但共同组合成了一个树形结构,那么对象之间就一定存在 “部分 - 整体” 的关系,组合模式要求我们抽象一个对象 `Component` 作为统一操作模型,叶子结点与非叶子结点都实现了所有功能,即便是没有子元素的叶子结点,为了强调透明性,还是具备比如 `getChildren` 方法,只不过永远都返回 `null`。 + +## 结构图 + + + +其中 `Component` 是组合中对象声明接口,一般会实现所有公共类的所有接口,还要提供一个接口管理其子组件。 + +`Leaf` 表示叶子结点,没有子结点,相应的 `Composite` 就是有子结点的节点。 + +可以看到,组合模式就是将树状结构中所有节点统一抽象了,**我们不需要关心叶子结点与非叶子结点的差异,而可以通过组合模式的抽象屏蔽掉这些差异,统一处理。** + +## 代码例子 + +下面例子使用 typescript 编写。 + +```typescript +// 统一的抽象 +class Component { + // 添加子元素 + public add() {} + // 获取名称 + public getName() {} + // 获取子元素 + public getChildren() {} +} + +// 非叶子结点 +class Composite extends Component { + public add(component: Component) { + this.children.push(component) + } + + public getName() { + return this.name + } + + public getChildren() { + return this.children + } +} + +// 叶子结点 +class Leaf extends Component { + public add(component: Component) { + throw Error('叶子结点无法添加元素') + } + + public getName() { + return this.name + } + + public getChildren() { + return null + } +} +``` + +最后我们把对所有节点的操作都转为 `Component` 对象,而不用关心这个对象具体是 `Composite` 或 `Leaf`。 + +## 弊端 + +组合模式进行了一层抽象,其实增加了复杂系统中业务复杂度。如果 `Composite` 与 `Leaf` 差异过大,那么统一抽象带来的理解成本是很高的。 + +同时,`Leaf` 不得不实现一些仅 `Composite` 存在的空函数,比如 `add` `delete`,即便这些方法对他们是无意义的,此时可能要进行统一的无效或错误处理,才能使业务层真正不用感知他们的区别,否则 `add` 可能会失败,其本质上还是将节点的区别暴露给了业务层。 + +## 总结 + +组合模式是针对树状结构这个特定场景的统一抽象方案,对降低系统复杂度有很重要的意义,同时也不要忘了过度抽象是有害的,我们要拿捏其中的度。 + +下图做了一个简单的解释: + + + +程序中始终关注 `Component` 就行了,树状结构的差异已经被抹平。 + +> 讨论地址是:[精读《设计模式 - Composite 组合模式》· Issue #284 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/284) + +**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。** + +> 关注 **前端精读微信公众号** + + + +> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))