forked from ascoders/weekly
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
111 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# Composite(组合模式) | ||
|
||
Composite(组合模式)属于结构型模式,是一种统一管理树形结构的抽象方式。 | ||
|
||
**意图:将对象组合成树形结构以表示 “部分 - 整体” 的层次结构。Composite 使得用户对单个对象和组合对象的使用具有一致性。** | ||
|
||
## 举例子 | ||
|
||
如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。 | ||
|
||
### 公司组织关系树 | ||
|
||
公司组织关系可能分为部门与人,其中人属于部门,有的人有下属,有的人没有下属。如果我们统一将部门、人抽象为组织节点,就可以方便的统计某个部门下有多少人、财务数据等等,而不用关心当前节点是部门还是人。 | ||
|
||
### 操作系统的文件夹与文件 | ||
|
||
操作系统的文件夹与文件也是典型的树状结构,为了方便递归出文件夹内文件数量或者文件总大小,我们最好设计的时候就将文件夹与文件抽象为文件,这样每个节点都拥有相同的方法添加、删除、查找子元素,而不需要关心当前节点是文件夹或是文件。 | ||
|
||
### 搭建平台的组件与容器 | ||
|
||
容器与组件的关系很小,用户常常认为容器也是一种组件,但搭建平台实现时,容器与组件稍有不同,不同之处在于容器可以嵌套子元素,而组件不可以。如果因此搭建平台就将组件分为容器与组件,会导致 API 割裂为两套,不利于组件开发者维护与用户理解,比较好的设计思路是将组件与容器统一看成组件,组件只是一种没有子元素的特殊容器,这样组件与容器就可以拥有相同的 API,统一理解与操作了。 | ||
|
||
## 意图解释 | ||
|
||
**意图:将对象组合成树形结构以表示 “部分 - 整体” 的层次结构。Composite 使得用户对单个对象和组合对象的使用具有一致性。** | ||
|
||
比较好理解,组合是指多个对象虽然有一定差异,但共同组合成了一个树形结构,那么对象之间就一定存在 “部分 - 整体” 的关系,组合模式要求我们抽象一个对象 `Component` 作为统一操作模型,叶子结点与非叶子结点都实现了所有功能,即便是没有子元素的叶子结点,为了强调透明性,还是具备比如 `getChildren` 方法,只不过永远都返回 `null`。 | ||
|
||
## 结构图 | ||
|
||
<img width=600 src="https://img.alicdn.com/tfs/TB19t0j27Y2gK0jSZFgXXc5OFXa-1504-678.png"> | ||
|
||
其中 `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` 可能会失败,其本质上还是将节点的区别暴露给了业务层。 | ||
|
||
## 总结 | ||
|
||
组合模式是针对树状结构这个特定场景的统一抽象方案,对降低系统复杂度有很重要的意义,同时也不要忘了过度抽象是有害的,我们要拿捏其中的度。 | ||
|
||
下图做了一个简单的解释: | ||
|
||
<img width=500 src="https://img.alicdn.com/tfs/TB1_g24rvzO3e4jSZFxXXaP_FXa-1228-614.png"> | ||
|
||
程序中始终关注 `Component` 就行了,树状结构的差异已经被抹平。 | ||
|
||
> 讨论地址是:[精读《设计模式 - Composite 组合模式》· Issue #284 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/284) | ||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。** | ||
|
||
> 关注 **前端精读微信公众号** | ||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg"> | ||
|
||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)) |