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

作者: YoungManSter | 来源:发表于2018-03-08 16:32 被阅读38次

    小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.缺点

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

    五、总结

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

    Android技术交流吧

    相关文章

      网友评论

        本文标题:设计模式系列之「组合模式」

        本文链接:https://www.haomeiwen.com/subject/rplsfftx.html