将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性,以便能够让客户端以一致的方式处理个别对象以及组合对象。
组合模式也称为部分整体模式,通过抽象出部分与整体的公共部分接口,然后达到部分与整体在被操作使用上的一致性,用户忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象。
看下组合模式的组成
- 抽象构件角色Component:它为组合中的对象声明接口,定义参加组合对象的共有方法和属性,可以定义一些默认的函数或属性。
- 树叶构件角色Leaf:在组合中表示叶子节点,实现抽象构件角色声明的接口。
- 树枝构件角色Composite:在组合中表示分支节点对象——有子节点,实现抽象构件角色声明的接口.它的作用是组合树枝节点和叶子节点形成一个树形结构。
安全性组合与透明性组合
组合模式中必须提供树枝节点对子对象的管理方法,不然无法完成对子对象的添加删除等等操作,也就失去了灵活性和扩展性。这里就涉及到这些方法是定义在Compnent中还是定义在Compsite中?
一种方式是在Component里面声明所有的用来管理子类对象的方法,以达到Component接口的最大化(如下图所示)。目的就是为了使客户看来在接口层次上树叶和分支没有区别——透明性。但树叶是不存在子类的,因此Component声明的一些方法对于树叶来说是不适用的。这样也就带来了一些安全性问题。
另一种方式就是只在Composite里面声明所有的用来管理子类对象的方法(如下图所示)。这样就避免了上一种方式的安全性问题,但是由于叶子和分支有不同的接口,所以又失去了透明性。
《设计模式》一书认为:在这一模式中,相对于安全性,我们比较强调透明性。对于第一种方式中叶子节点内不需要的方法可以使用空处理或者异常报告的方式来解决。
这里在看的时候也看到两个不错的实例,一个是针对公司、部门的实例。另外一个是在Android中View和ViewGroup的例子,都是组合模式的代表。这里我想到了Dota中的一个场景。Dota中以英雄做核心,英雄又分为力量、敏捷、智力,各个类型的英雄又有很多个实现。
1、抽象所有英雄的操作HeroComponent接口
public abstract class HeroCompnent {
/**
* 添加
*/
public abstract void add(HeroCompnent heroCompnent);
/**
* 移动
*/
public abstract void move();
/**
* 攻击
*/
public abstract void attack();
}
2、定义叶子节点,比如实现一个英雄-影魔
/**
* 影魔类
* @author Iflytek_dsw
*
*/
public class YaphetsLeaf extends HeroCompnent{
@Override
public void move() {
System.out.println("Yaphets move");
}
@Override
public void attack() {
System.out.println("Yaphets attack");
}
@Override
public void add(HeroCompnent heroCompnent) {
//这里是一个缺省的方法
}
}
3、定义智力英雄
/**
* 智力英雄
* @author Iflytek_dsw
*
*/
class IntellectualHero extends HeroCompnent{
private List<HeroCompnent> listIntell = new ArrayList<>();
public IntellectualHero(){
}
@Override
public void move() {
for(HeroCompnent tempCompnent : listIntell){
tempCompnent.move();
}
}
@Override
public void attack() {
for(HeroCompnent tempCompnent : listIntell){
tempCompnent.attack();
}
}
@Override
public void add(HeroCompnent heroCompnent) {
listIntell.add(heroCompnent);
}
}
/**
* 沉默术士
* @author Iflytek_dsw
*
*/
class SilenceHero extends IntellectualHero{
@Override
public void move() {
System.out.println("SilenceHero move");
}
@Override
public void attack() {
System.out.println("SilenceHero attack");
}
}
/**
* 食人魔法师
* @author Iflytek_dsw
*
*/
class AggronStonebreaker extends IntellectualHero{
@Override
public void move() {
System.out.println("AggronStonebreaker move");
}
@Override
public void attack() {
System.out.println("AggronStonebreaker attack");
}
}
4、定义所有英雄的Compsite
/**
* 所有的英雄
* @author Iflytek_dsw
*
*/
public class HeroCompsite extends HeroCompnent {
private List<HeroCompnent> listHeroCompsite = new ArrayList<HeroCompnent>();
@Override
public void add(HeroCompnent heroCompnent) {
listHeroCompsite.add(heroCompnent);
}
@Override
public void move() {
for(HeroCompnent tempCompnent : listHeroCompsite){
tempCompnent.move();
}
}
@Override
public void attack() {
for(HeroCompnent tempCompnent : listHeroCompsite){
tempCompnent.attack();
}
}
}
定义客户端
public class Client {
public static void main(String []args){
/**
* 首先,我们也需要明确一点,我们把这些逻辑设计好,但是他们之间的关系、组装还是需要单独维护的。
* 不然如果新增一个种类,都去在Compsite中维护显然违背了面向对象的设计原则,改动过大。
*/
//客户想看看影魔的攻击
HeroCompnent yaphetsLeaf = new YaphetsLeaf();
yaphetsLeaf.attack();
//客户想看下沉默术士的移动效果
HeroCompnent silence = new SilenceHero();
silence.move();
//客户想了解下所有智力英雄的攻击效果
IntellectualHero intellectualHero = new IntellectualHero();
intellectualHero.add(new SilenceHero());
intellectualHero.add(new AggronStonebreaker());
intellectualHero.attack();
}
}
在上面的例子中,我们能看到组合模式可以很灵活的组合成我们需要的操作。只需要把关系理清楚,然后组合好。客户端只需要进行按照需要添加元素来进行操作,不需要关注逻辑关系。
总结
在学习组合模式过程中,看了网上很多文章的介绍,但总是理解的不是很透彻。总是理解“整体-部分”的概念理解不是很透彻。这个整体理解也是一个大种类,这个大类里面可以包含很多小类,操作这个类别的时候,直接操作这个整体。
维护一个技术更新确实非常难,但是看到大家的点赞、收藏很开心,希望大家多多支持。同时欢迎大家留言交流,共同进步。