设计模式系列之「组合模式」

小Y科普:家谱又称族谱、宗谱等。它以记载父系家族世系、人物为中心,由正史中的帝王本纪及王侯列传、年表等演变而来。是一种特殊的文献,就其内容而言,是中国五千年文明史中具有平民特色的文献,记载的是同宗共祖血缘集团世系人物和事迹等方面情况的历史图籍。

Now,how to 实现 小J's 族谱。

一、小J族谱简略版

从最顶层的第一代J开始,一代代往下记录下去,这很明显就是一个树状结构,现在小Y要做的就是通过最合适的方式把小J的族谱图遍历出来。

二、树状图的拆解利器-组合模式

1.组合模式的定义

组合模式也叫合成模式,有时又叫做部分-整体模式,主要是用来描述部分与整体的关系,将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

2.组合模式的角色介绍(组合模式有两种实现:安全模式和透明模式)

  • Component抽象构件角色
    定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性。

  • Leaf叶子构件
    Leaf叶子构件叶子对象,其下再也没有其他的分支,也就是遍历的最小单位。

  • Composite树枝构件
    树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。组合模式的重点就在树枝构件。

3.组合模式的使用场景

  • 只要是树形结构或者只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,就要考虑一下组合模式。

  • 从一个整体中能够独立出部分模块或功能的场景。

  • 维护和展示部分-整体关系的场景。

4.安全模式和透明模式的具体实现

(1)安全模式

①抽象构件

public abstract class Component {
    //个体和整体都具有
    public void operation(){
        //编写业务逻辑
    }
}

②树枝构件

public class Composite extends Component {
    //构件容器
    private List<Component> componentArrayList = new ArrayList<Component>();
    //增加一个叶子构件或树枝构件
    public void add(Component component){
        this.componentArrayList.add(component);
    }
    //删除一个叶子构件或树枝构件
    public void remove(Component component){
        this.componentArrayList.remove(component);
    }
    //获得分支下的所有叶子构件和树枝构件
    public List<Component> getChildren(){
        return this.componentArrayList;
    }
}

③树叶构件

public class Leaf extends Component {
    /*
    * 可以覆写父类方法
    * public void operation(){
    *
    * }
    */
}

④Client

public class Client {
    public static void main(String[] args) {
        //创建一个根节点
        Composite root = new Composite();
        root.operation();
        //创建一个树枝构件
        Composite branch = new Composite();
        //创建一个叶子节点
        Leaf leaf = new Leaf();
        //建立整体
        root.add(branch);
        branch.add(leaf);
    }

    //通过递归遍历树
    public static void showTree(Composite root){
        for(Component c:root.getChildren()){
            if(c instanceof Leaf){ //叶子节点
                c.operation();
            }else{ //树枝节点
                showTree((Composite)c);
            }
        }
    }
}

(2)透明模式

①抽象构件

public abstract class Component {
    //个体和整体都具有
    public void operation(){
        //编写业务逻辑
    }
    //增加一个叶子构件或树枝构件
    public abstract void add(Component component);
    //删除一个叶子构件或树枝构件
    public abstract void remove(Component component);
    //获得分支下的所有叶子构件和树枝构件
    public abstract List<Component> getChildren();
}

②树枝构件

public class Composite extends Component {
    //构件容器
    private ArrayList<Component> componentArrayList = new ArrayList<Component>();
    //增加一个叶子构件或树枝构件
    public void add(Component component){
        this.componentArrayList.add(component);
    }
    //删除一个叶子构件或树枝构件
    public void remove(Component component){
        this.componentArrayList.remove(component);
    }
    //获得分支下的所有叶子构件和树枝构件
    public List<Component> getChildren(){
        return this.componentArrayList;
    }
}

③树叶构件

public class Leaf extends Component {

    public void add(Component component){
        //空实现
    }

    public void remove(Component component){
        //空实现
    }

    public List<Component> getChildren(){
        //空实现
    }
}

④Client

public class Client {

    public static void main(String[] args) {
        //创建一个根节点
        Composite root = new Composite();
        root.operation();
        //创建一个树枝构件
        Composite branch = new Composite();
        //创建一个叶子节点
        Leaf leaf = new Leaf();
        //建立整体
        root.add(branch);
        branch.add(leaf);
    }

    //通过递归遍历树
    public static void showTree(Component root){
        for(Component c:root.getChildren()){
            if(c instanceof Leaf){ //叶子节点
                c.operation();
            }else{ //树枝节点
                showTree(c);
            }
        }
    }
}

4.安全模式和透明模式的区别

  • 安全模式在抽象组件中只定义一些默认的行为或属性,它是把树枝节点和树叶节点彻底分开;透明模式是把用来组合使用的方法放到抽象类中,不管叶子对象还是树枝对象都有相同的结构,通过判断确认是叶子节点还是树枝节点,如果处理不当,这个会在运行期出现问题,不是很建议的方式。

  • 安全模式与依赖倒置原则冲突;透明模式的好处就是它基本遵循了依赖倒转原则,方便系统进行扩展。

  • 安全模式在遍历树形结构的的时候需要进行强制类型转换;在透明模式下,遍历整个树形结构是比较容易的,不用进行强制类型转换。

三、通过安全模式实现遍历小J的族谱

①抽象构件抽象族员类

public abstract class PersonMode {
    //人名
    private String name;
    //性别
    private String sex;
    //年龄
    private int age;

    public PersonMode(String name, String sex, int age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    //个人信息
    public String getPersonInfo(){
        String info="姓名:"+name+"\t性别:"+sex+"\t年龄:"+age;
        return info;
    }
}

②树叶构件

public class PersonLeaf extends PersonMode {
    //写一个构造函数
    public PersonLeaf(String name, String sex, int age) {
        super(name, sex, age);
    }
}

③树枝构件

public class PersonBranch extends PersonMode {
    private List<PersonMode> personModeList=new ArrayList<>();

    public PersonBranch(String name, String sex, int age) {
        super(name, sex, age);
    }

    public void addPerson(PersonMode person){
        this.personModeList.add(person);
    }

    public List<PersonMode> getPersonModeList(){
        return this.personModeList;
        }
}

④Client

public class Client {
    public static void main(String[] args) {
        /**
         * 组装小J的族谱
        */
        PersonBranch personBranch=getPersonInfo();
        showTree(personBranch);
    }

    private static PersonBranch getPersonInfo(){
        //第一代J
        PersonBranch OneJ=new PersonBranch("第一代J","男",150);
        //第一代J的三个儿子
        PersonBranch JA=new PersonBranch("JA","男",70);
        PersonBranch JB=new PersonBranch("JB","男",60);
        PersonBranch JC=new PersonBranch("JC","男",50);
        //JA的三个儿子
        PersonBranch JA1=new PersonBranch("JA1","男",40);
        PersonBranch JA2=new PersonBranch("JA2","男",30);
        PersonBranch JA3=new PersonBranch("JA3","男",45);
        //JB的两个儿子
        PersonBranch JB1=new PersonBranch("JB1","男",40);
        PersonBranch JB2=new PersonBranch("JB2","男",30);
        //JC的儿子小J
        PersonBranch xiao_J=new PersonBranch("xiao_J","男",20);
        //JA1三个儿子
        PersonBranch JA1_1=new PersonBranch("JA1_1","男",18);
        PersonBranch JA1_2=new PersonBranch("JA1_2","男",16);
        PersonBranch JA1_3=new PersonBranch("JA1_3","男",20);
        //JA3三个儿子
        PersonBranch JA3_1=new PersonBranch("JA3_1","男",16);
        PersonBranch JA3_2=new PersonBranch("JA3_2","男",20);
        PersonBranch JA3_3=new PersonBranch("JA3_3","男",18);

        //开始组装树状族谱
        //组装第一代J下的三个儿子
        OneJ.addPerson(JA);
        OneJ.addPerson(JB);
        OneJ.addPerson(JC);
        //组装JA的三个儿子
        JA.addPerson(JA1);
        JA.addPerson(JA2);
        JA.addPerson(JA3);
        //组装JB的两个儿子
        JB.addPerson(JB1);
        JB.addPerson(JB2);
        //组装JC的儿子
        JC.addPerson(xiao_J);
        //组装JA1的三个儿子
        JA1.addPerson(JA1_1);
        JA1.addPerson(JA1_2);
        JA1.addPerson(JA1_3);
        //组装JA3的三个儿子
        JA3.addPerson(JA3_1);
        JA3.addPerson(JA3_2);
        JA3.addPerson(JA3_3);

        return OneJ;
    }

    //通过递归遍历树
    private static void showTree(PersonBranch root){
        System.out.println(root.getPersonInfo());
        for(PersonMode c:root.getPersonModeList()){
            if(c instanceof PersonLeaf){ //叶子节点
                System.out.println(c.getPersonInfo());
            }else{ //树枝节点
                showTree((PersonBranch) c);
            }
        }
    }
}

场景类负责树状结构的建立,并可以通过递归方式遍历整个树。

输出的结果为:

姓名:第一代J 性别:男 年龄:150
姓名:JA 性别:男 年龄:70
姓名:JA1 性别:男 年龄:40
姓名:JA1_1 性别:男 年龄:18
姓名:JA1_2 性别:男 年龄:16
姓名:JA1_3 性别:男 年龄:20
姓名:JA2 性别:男 年龄:30
姓名:JA3 性别:男 年龄:45
姓名:JA3_1 性别:男 年龄:16
姓名:JA3_2 性别:男 年龄:20
姓名:JA3_3 性别:男 年龄:18
姓名:JB 性别:男 年龄:60
姓名:JB1 性别:男 年龄:40
姓名:JB2 性别:男 年龄:30
姓名:JC 性别:男 年龄:50
姓名:xiao_J 性别:男 年龄:20

四、组合模式优缺点

1.优点

高层模块调用简单。局部和整体对调用者来说没有任何区别,也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。

节点自由增加。使用了组合模式后,我们可以看看,如果想增加一个树枝节点、树叶节点十分简单,只要找到它的父节点就成,非常容易扩展,符合开闭原则,对以后的维护非常有利。

2.缺点

组合模式有一个非常明显的缺点,在上面的场景类可以看到树枝树叶直接使用了实现类,这在面向接口编程上是很不恰当的,与依赖倒置原则冲突,它限制了你接口的影响范围。

五、总结

只要是树形结构或者只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,,就要考虑使用组合模式。

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

推荐阅读更多精彩内容