设计模式之组合模式

一、什么是组合模式

组合模式(Composite),将对象组合成树形结构以表示“部分-整体”的层次结构,用户对单个对象和组合对象的使用具有一致性。

所以当我们的案例是树形结构或者是部分-整体的关系时,就可以考虑使用组合模式。

组合模式有两种不同的实现,分别为透明模式和安全模式,下面将详细说明一下两种实现的区别。

先说明一下UML图中各角色的职责。Component是对象声明接口,在适当情况下,实现所有类共有接口的默认行为;Leaf是叶子节点对象,其没有子节点;Composite是树枝节点对象,用来存储部件,组合树枝节点和叶子节点形成一个树形结构。

下面这两种方式我们共用同一套客户端,先将客户端代码放上。

1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         //创建根节点及其子节点
 5         Composite root = new Composite("root");
 6         root.add(new Leaf("Leaf A"));
 7         root.add(new Leaf("Leaf B"));
 8 
 9         //创建第二层节点及其子节点
10         Composite branch = new Composite("Composite X");
11         branch.add(new Leaf("Leaf XA"));
12         branch.add(new Leaf("Leaf XB"));
13         root.add(branch);
14         
15         //创建第三层节点及其子节点
16         Composite branch2 = new Composite("Composite XY");
17         branch2.add(new Leaf("Leaf XYA"));
18         branch2.add(new Leaf("Leaf XYB"));
19         branch.add(branch2);
20         
21         //创建第二层节点
22         root.add(new Leaf("Leaf C"));
23         
24         //创建第二层节点并删除
25         Leaf leaf = new Leaf("Leaf D");
26         root.add(leaf);
27         root.remove(leaf);
28         
29         //打印
30         root.display(1);
31     }
32     
33 }

二、组合模式之透明模式

透明模式是把组合使用的方法放到抽象类中,不管叶子对象还是树枝对象都有相同的结构,这样做的好处就是叶子节点和树枝节点对于外界没有区别,它们具备完全一致的行为接口。但因为Leaf类本身不具备add()、remove()方法的功能,所以实现它是没有意义的。UML结构图如下:

ThirdPartyImage_a95b15e9.png

1. Component

1 public abstract class Component {
 2     
 3     protected String name;
 4     
 5     public Component(String name) {
 6         this.name = name;
 7     }
 8 
 9     //增加一个叶子构件或树枝构件
10     public abstract void add(Component component);
11     
12     //删除一个叶子构件或树枝构件
13     public abstract void remove(Component component);
14     
15     //获取分支下的所有叶子构件和树枝构件
16     public abstract void display(int depth);
17     
18 }

2. Composite

1 public class Composite extends Component {
 2 
 3     public Composite(String name) {
 4         super(name);
 5     }
 6 
 7     //构建容器
 8     private ArrayList<Component> componentArrayList = new ArrayList<Component>();
 9     
10     @Override
11     public void add(Component component) {
12         this.componentArrayList.add(component);
13     }
14 
15     @Override
16     public void remove(Component component) {
17         this.componentArrayList.remove(component);
18     }
19 
20     @Override
21     public void display(int depth) {
22         //输出树形结构
23         for(int i=0; i<depth; i++) {
24             System.out.print('-');
25         }
26         System.out.println(name);
27         
28         //下级遍历
29         for (Component component : componentArrayList) {
30             component.display(depth + 1);
31         }
32     }
33 
34 }

3. Leaf

1 public class Leaf extends Component {
 2 
 3     public Leaf(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void add(Component component) {
 9         //空实现,抛出“不支持请求”异常
10         throw new UnsupportedOperationException();
11     }
12 
13     @Override
14     public void remove(Component component) {
15         //空实现,抛出“不支持请求”异常
16         throw new UnsupportedOperationException();
17     }
18 
19     @Override
20     public void display(int depth) {
21         //输出树形结构的叶子节点
22         for(int i=0; i<depth; i++) {
23             System.out.print('-');
24         }
25         System.out.println(name);
26     }
27 
28 }

通过组合模式输出一个树形结构,运行结果如下:

ThirdPartyImage_d52ff8de.png

三、组合模式之安全模式

安全模式是把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全。但由于不够透明,所以树叶节点和树枝节点将不具有相同的接口,客户端的调用需要做相应的判断,带来了不便。UML结构图如下:

ThirdPartyImage_a6a35e3a.png

1. Component

这里相比透明模式就少了add()和romove()抽象方法的声明。

1 public abstract class Component {
 2     
 3     protected String name;
 4     
 5     public Component(String name) {
 6         this.name = name;
 7     }
 8 
 9     //获取分支下的所有叶子构件和树枝构件
10     public abstract void display(int depth);
11     
12 }

2. Composite

这里add()和remove()方法的实现就从继承变为了自己实现。

1 public class Composite extends Component {
 2 
 3     public Composite(String name) {
 4         super(name);
 5     }
 6 
 7     //构建容器
 8     private ArrayList<Component> componentArrayList = new ArrayList<Component>();
 9     
10     //增加一个叶子构件或树枝构件
11     public void add(Component component) {
12         this.componentArrayList.add(component);
13     }
14 
15     //删除一个叶子构件或树枝构件
16     public void remove(Component component) {
17         this.componentArrayList.remove(component);
18     }
19 
20     @Override
21     public void display(int depth) {
22         //输出树形结构
23         for(int i=0; i<depth; i++) {
24             System.out.print('-');
25         }
26         System.out.println(name);
27         
28         //下级遍历
29         for (Component component : componentArrayList) {
30             component.display(depth + 1);
31         }
32     }
33 
34 }

3. Leaf

叶子节点中没有了空实现,比较安全。

1 public class Leaf extends Component {
 2 
 3     public Leaf(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void display(int depth) {
 9         //输出树形结构的叶子节点
10         for(int i=0; i<depth; i++) {
11             System.out.print('-');
12         }
13         System.out.println(name);
14     }
15 
16 }

运行结果如下:

ThirdPartyImage_02367797.png

由此可看出两个方法是相同的运行结果,区别在于内部实现不同,一种是叶节点与树枝节点具备一致的行为接口但有空实现的透明模式,另一种是树枝节点单独拥有用来组合的方法但调用不便的安全模式。

为什么说它调用不便呢,因为我们如果通过递归遍历树时,这时需要判断当前节点是叶子节点还是树枝节点,客户端就需要相应的判断。

四、组合模式的应用

1. 何时使用

  • 想表达“部分-整体”层次结构(树形结构)时
  • 希望用户忽略组合对象与单个对象的不同,用户将统一的使用组合结构中的所有对象

2. 方法

  • 树枝和叶子实现统一接口,树枝内部组合该接口

3. 优点

  • 高层模块调用简单。一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,高层模块不必关心自己处理的是单个对象还是整个组合结构。
  • 节点自由增加

4. 缺点

  • 使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒转原则

5. 使用场景

  • 维护和展示部分-整体关系的场景(如树形菜单、文件和文件夹管理)
  • 从一个整体中能够独立出部分模块或功能的场景

五、组合模式的实现

下面我们以公司的层级结构为例,先看一下这个例子中该公司的层级结构(该例选自大话设计模式——程杰著)。

ThirdPartyImage_7ec8008e.png

这种部分与整体的关系,我们就可以考虑使用组合模式,下面采用组合模式的透明模式对其实现,UML图如下:

ThirdPartyImage_77cb0992.png

1. 具体公司类

此为树枝节点,实现添加、移除、显示和履行职责四种方法。

1 public class ConcreteCompany extends Company {
 2 
 3     private List<Company> companyList = new ArrayList<Company>();
 4     
 5     public ConcreteCompany(String name) {
 6         super(name);
 7     }
 8 
 9     @Override
10     public void add(Company company) {
11         this.companyList.add(company);
12     }
13 
14     @Override
15     public void remove(Company company) {
16         this.companyList.remove(company);
17     }
18 
19     @Override
20     public void display(int depth) {
21         //输出树形结构
22         for(int i=0; i<depth; i++) {
23             System.out.print('-');
24         }
25         System.out.println(name);
26         
27         //下级遍历
28         for (Company component : companyList) {
29             component.display(depth + 1);
30         }
31     }
32 
33     @Override
34     public void lineOfDuty() {
35         //职责遍历
36         for (Company company : companyList) {
37             company.lineOfDuty();
38         }
39     }
40 
41 }

2. 人力资源部

叶子节点,add和remove方法空实现。

1 public class HRDepartment extends Company {
 2 
 3     public HRDepartment(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void add(Company company) {
 9         
10     }
11 
12     @Override
13     public void remove(Company company) {
14         
15     }
16 
17     @Override
18     public void display(int depth) {
19         //输出树形结构的子节点
20         for(int i=0; i<depth; i++) {
21             System.out.print('-');
22         }
23         System.out.println(name);
24     }
25 
26     @Override
27     public void lineOfDuty() {
28         System.out.println(name + " : 员工招聘培训管理");
29     }
30     
31 }

3. 财务部

叶子节点,add和remove方法空实现。

1 public class FinanceDepartment extends Company {
 2     
 3     public FinanceDepartment(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void add(Company company) {
 9         
10     }
11 
12     @Override
13     public void remove(Company company) {
14         
15     }
16 
17     @Override
18     public void display(int depth) {
19         //输出树形结构的子节点
20         for(int i=0; i<depth; i++) {
21             System.out.print('-');
22         }
23         System.out.println(name);
24     }
25 
26     @Override
27     public void lineOfDuty() {
28         System.out.println(name + " : 公司财务收支管理");
29     }
30     
31 }

4. Client客户端

1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         //总公司
 5         ConcreteCompany root = new ConcreteCompany("北京总公司");
 6         root.add(new HRDepartment("总公司人力资源部"));
 7         root.add(new FinanceDepartment("总公司财务部"));
 8         
 9         //分公司
10         ConcreteCompany company = new ConcreteCompany("上海华东分公司");
11         company.add(new HRDepartment("华东分公司人力资源部"));
12         company.add(new FinanceDepartment("华东分公司财务部"));
13         root.add(company);
14         
15         //办事处
16         ConcreteCompany company1 = new ConcreteCompany("南京办事处");
17         company1.add(new HRDepartment("南京办事处人力资源部"));
18         company1.add(new FinanceDepartment("南京办事处财务部"));
19         company.add(company1);
20         
21         ConcreteCompany company2 = new ConcreteCompany("杭州办事处");
22         company2.add(new HRDepartment("杭州办事处人力资源部"));
23         company2.add(new FinanceDepartment("杭州办事处财务部"));
24         company.add(company2);
25         
26         System.out.println("结构图:");
27         root.display(1);
28         
29         System.out.println("\n职责:");
30         root.lineOfDuty();
31     }
32     
33 }

运行结果如下:

ThirdPartyImage_dfb88d21.png

组合模式这样就定义了包含人力资源部和财务部这些基本对象和分公司、办事处等组合对象的类层次结构。

基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户代码中,任何用到基本对象的地方都可以使用组合对象了。

这里用了透明模式,用户不用关心到底是处理一个叶节点还是处理一个组合组件,也就用不着为定义组合而写一些选择判断语句了。简单点说就是组合模式可以让客户一致地使用组合结构和单个对象。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容

  • 前言 Android的设计模式系列文章介绍,欢迎关注,持续更新中: Android的设计模式-设计模式的六大原则一...
    四月葡萄阅读 4,636评论 1 14
  • 基本介绍 定义 将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。...
    敏捷Studio阅读 527评论 0 0
  • 组合模式介绍 组合模式(Composite Pattern)也称为部分整体模式(Part-Whole Patter...
    小的橘子阅读 1,016评论 0 0
  • 1.组合模式的定义及使用场景组合模式也称为部分整体模式,结构型设计模式之一,组合模式比较简单,它将一组相似的对象看...
    GB_speak阅读 860评论 0 2
  • 组合模式(Composite) 在现实生活中,存在很多“部分-整体”的关系,例如,大学中的部门与学院、总公司中的部...
    Acton_zhang阅读 400评论 0 1