精华内容
下载资源
问答
  • 设计模式--组合模式java例子
  • 设计模式-组合模式(讲解及其实现代码)
  • java组合模式例子

    2015-01-07 13:49:49
    java组合模式例子
  • 组合模式

    千次阅读 2018-05-24 10:42:21
    定义: 将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户能以一致的方式处理个别对象和组合对象。 设计类图: 组合模式中的角色: 列表内容 ...

    定义:

    将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户能以一致的方式处理个别对象和组合对象。

    设计类图:

    组合模式中的角色:

    • Component抽象组件:为组合中所有对象提供一个接口,不管是叶子对象还是组合对象。
    • Composite组合节点对象:实现了Component的所有操作,并且持有子节点对象。
    • Leaf叶节点对象:叶节点对象没有任何子节点,实现了Component中的某些操作。

    组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象。

    使用组合结构,我们能把相同的操作应用在组合和个别对象上。换句活说,在大多数情况下,我们可以忽略对象组合和个别对象之问的差别。

    示例代码:

    public abstract class Component {
        protected String name;
    
        public Component(String name) {
            this.name = name;
        }
    
        public abstract void operation();
    
       public void add(Component c) {
            throw new UnsupportedOperationException();
        }
    
        public void remove(Component c) {
            throw new UnsupportedOperationException();
        }
    
        public Component getChild(int i) {
            throw new UnsupportedOperationException();
        }
    
        public List<Component> getChildren() {
            return null;
        }
    }
    public class Composite extends Component {
        private List<Component> components = new ArrayList<>();
    
        public Composite(String name) {
            super(name);
        }
    
        @Override
        public void operation() {
            System.out.println("组合节点"+name+"的操作");
            //调用所有子节点的操作
            for (Component component : components) {
                 component.operation();
            }
        }
    
        @Override
        public void add(Component c) {
            components.add(c);
        }
    
        @Override
        public void remove(Component c) {
            components.remove(c);
        }
    
        @Override
        public Component getChild(int i) {
            return components.get(i);
        }
    
        @Override
        public List<Component> getChildren() {
            return components;
        }
    }
    public class Leaf extends Component {
    
        public Leaf(String name) {
            super(name);
        }
    
        @Override
        public void operation() {
            System.out.println("叶节点"+name+"的操作");
        }
    }
    public class Client {
        public static void main(String[] args) {
            //创建根节点对象
            Component component = new Composite("component");
    
            //创建两个组合节点对象
            Component composite1 = new Composite("composite1");
            Component composite2 = new Composite("composite2");
    
            //将两个组合节点对象添加到根节点
            component.add(composite1);
            component.add(composite2);
    
            //给第一个组合节点对象添加两个叶子节点
            Component leaf1 = new Leaf("leaf1");
            Component leaf2 = new Leaf("leaf2");
            composite1.add(leaf1);
            composite1.add(leaf2);
    
            //给第二个组合节点对象添加一个叶子节点和一个组合节点
            Component leaf3 = new Leaf("leaf3");
            Component composite3 = new Composite("composite3");
            composite2.add(leaf3);
            composite2.add(composite3);
    
            //给第二个组合节点下面的组合节点添加两个叶子节点
            Component leaf4 = new Leaf("leaf4");
            Component leaf5 = new Leaf("leaf5");
            composite3.add(leaf4);
            composite3.add(leaf5);
    
            //执行所有节点的操作
            component.operation();
        }
    }

    输出结果:
    这里写图片描述
      上述代码中,在组合节点对象Composite的operation()方法中除了执行自身的操作外,还调用了子节点的operation()方法,这样使得客户端可以透明的遍历所有的节点对象的操作,而不用关心操作的是叶子节点还是组合节点,将它们一视同仁。这看上去有点像二叉树的遍历,不过这里并不是二叉树,每个组合节点可以有若干个子节点,而这些子节点,如果是组合节点,则可以继续拥有子节点,如果是叶子节点,那么就终止了。

      叶子节点和组合节点可以有相同的操作,如上面代码中的operation()方法,但是叶子节点不具备add、remove以及getChild操作,如果你试图在叶子节点上调用这些方法就会抛出不支持的异常。组合节点可以添加子节点,因此组合节点实现了add、remove以及getChild等操作。组合节点持有一个节点的集合,在组合节点的operation()方法中通过遍历调用持有节点的operation()方法,就像是在递归遍历一样。通过这种方式Client客户端可以透明的访问节点对象,你可以在客户端中调用一个组合节点的operation()方法,也可以调用一个叶子节点的operation()方法,也就是说你根本不需要关心调用的是组合节点还是叶子节点,它们都可以进行相同的操作。

    菜单的例子

      服务员需要打印菜单,如菜单的名称和价格,但是菜单既可以有子菜单组合,也可以有子菜单项,对于子菜单组合,它的下面又可能会有子菜单项,如饮料菜单和甜点菜单等会包含很多东西,而子菜单项就是一个具体的菜名,不会有子菜单了。现在要打印所有的菜单描述,我们用组合模式来实现这个功能:

    实现代码:

    /**
     * 抽象菜单组件
     */
    public abstract class MenuComponent {
    
        public void add(MenuComponent menu) {
            throw new UnsupportedOperationException();
        }
    
        public void remove(MenuComponent menu) {
            throw new UnsupportedOperationException();
        }
    
        public MenuComponent getChild(int i) {
            throw new UnsupportedOperationException();
        }
    
        public String getName() {
            throw new UnsupportedOperationException();
        }
    
        public double getPrice() {
            throw new UnsupportedOperationException();
        }
    
        public abstract void print();
    }
    /**
     * 菜单组件
     * 菜单组件有菜单名和子菜单,但没有价格,支持添加、删除和打印等操作
     */
    public class Menu extends MenuComponent {
        private List<MenuComponent> menuList = new ArrayList<>();
        private String name;
    
        public Menu(String name) {
            this.name = name;
        }
    
        @Override
        public void add(MenuComponent menu) {
            menuList.add(menu);
        }
    
        @Override
        public void remove(MenuComponent menu) {
            menuList.remove(menu);
        }
    
        @Override
        public MenuComponent getChild(int i) {
            return menuList.get(i);
        }
    
        @Override
        public String getName() {
            return name;
        }
    
        @Override
        public void print() {
            System.out.println("--------");
            System.out.println(getName());
            //打印所有子菜单
            for (MenuComponent menu : menuList) {
                 menu.print();
            }
            System.out.println("--------");
        }
    }
    /**
     * 菜单项
     * 菜单项拥有名称和价格,可以打印,但是不支持添加、删除等操作
     */
    public class MenuItem extends MenuComponent {
        private String name;
        private double price;
    
        public MenuItem(String name, double price) {
            this.name = name;
            this.price = price;
        }
    
        @Override
        public String getName() {
            return name;
        }
    
        @Override
        public double getPrice() {
            return price;
        }
    
        @Override
        public void print() {
            System.out.println(getName() + " -- " + getPrice());
        }
    }
    public class Client {
        public static void main(String[] args) {
            Menu menu = new Menu("所有菜单");
    
            Menu menu1 = new Menu("子菜单1");
            Menu menu2 = new Menu("子菜单2");
            Menu menu3 = new Menu("子菜单3");
    
            //给所有菜单添加三个子菜单
            menu.add(menu1);
            menu.add(menu2);
            menu.add(menu3);
    
            //给第二个菜单添加一个菜单项和一个子菜单
            menu2.add(new MenuItem("子菜单2--菜单项", 10.0));
            Menu menu4 = new Menu("子菜单2--子菜单");
            menu2.add(menu4);
            menu4.add(new MenuItem("子菜单2--子菜单--菜单项", 20.0));
    
            //打印所有菜单
            menu.print();
        }
    }

    打印结果:
    这里写图片描述
      在抽象菜单MenuComponent组件中,我们将一些操作默认抛出UnsupportedOperationException异常,如果子类支持该操作就重写实现该操作,如果子类不支持该操作,就不用管它。使用组合模式,打印菜单变得非常容易,而且更好的一点是,你现在可以拿任何一个子菜单来打印结果,而不用管它是具体的菜单项还是里面又包含了子菜单。如果不用组合模式,很难想象有一种方法能很方便的将所有的菜单打印出来。

    菜单例子中,既要管理层次结构,又要执行打印操作,是否破坏了单一职责?

      严格来说,是的。我们可以这么说,组合模式以单一职责换取透明性。 什么是透明性?通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合和叶节点 一视同仁。也就是说一个元素究竟是组合还是叶节点,对客户是透明的。

      现在,我们在 MenuComponent 类中同时具有两种类型的操作. 因为客户有机会对一个元素做一些没有意义的操作(例如试图把菜单添加到菜单项),所以我们失去 一些‘安会性”。这是设计上的抉择;我们当然也可以采用另一种方向的设计,将责任区分开来放在不同的接口中。这么一来,设计上就比较安全,但我们也因此失去了透明性,客户的代码将必须使用条件语句和 instanceof 操作符处理不同类型的节点。

      所以, 这是一个很典型的折衷案例。尽管我们受到设计原则的指导,但是,我们总是需要观察某原则对我们的设计所造成的影响。有时候,我们会故意做一些看似违反原则的事情。然而,在某些例子中,这是观点的问题。比方说让管理孩子的操作(例如 add ( )、 remove( )、 getchild ( ) )出现在叶节点中,似乎很不恰当,但是换个视角来看,你可以把叶布点视为没有孩子的节点。

    组合模式的扩展

    子节点可以指向父节点

      组件可以有一个指向父节点的引用,以便在游走时更容易。而且,如果引用某个孩子,你想从树形结构中删除这个孩子,你会需要从父节点中去蒯除它。一旦孩子有了指向父亲的引用,这做起来就很容易。这样做也使得遍历操作可上可下,更加自由灵活。

    使用缓存

      有时候,如果这个组合结构很复杂,或者遍历的代价太高,那么实现组合节点的缓存就很有帮助。比方说,如果你要不断地遍历一个组合,而且它的每一个子节点都需要进行某些计算,那你就应该使用缓存来临时保存结果,从而省去遍历的开支。

    组合模式应用场景

      这个应用的地方也比较多,比如大多数系统的UI界面的导航菜单一般都是组合模式,再如Android里面的xml布局都是用的组合模式。在选择是否应用组合模式时,要考虑设计上的抉择,到底是要透明性更多一点,还是安全性更多一点,需要做一个平衡。


    参考:

    展开全文
  • JavaScript 设计模式之组合模式

    万次阅读 2019-12-02 22:39:27
    比如公司要给全体员工发放元旦的过节费1000块,这个场景可以运用组合模式,但如果公司给今天过生日的员工发送一封生日祝福的邮件,组合模式在这里就没有用武之地了,除非先把今天过生日的员工挑选出来。只有用一致的...

    我们知道地球和一些其他行星围绕着太阳旋转,也知道在一个原子中,有许多电子围绕着原子核旋转。我曾经想象,我们的太阳系也许是一个更大世界里的一个原子,地球只是围绕着太阳原子的一个电子。而我身上的每个原子又是一个星系,原子核就是这个星系中的恒星,电子是围绕着恒星旋转的行星。一个电子中也许还包含了另一个宇宙,虽然这个宇宙还不能被显微镜看到,但我相信它的存在。

    也许这个想法有些异想天开,但在程序设计中,也有一些和“事物是由相似的子事物构成”类似的思想。组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成的。

    回顾宏命令

    宏命令对象包含了一组具体的子命令对象,不管是宏命令对象,还是子命令对象,都有
    一个execute方法负责执行命令。

    现在我们来造一个“万能遥控器”

    // 新建一个关门的命令
    var closeDoorCommand = {
        execute: function(){
            console.log( '关门' );
        }
    };
    // 新建一个开电脑的命令
    var openPcCommand = {
        execute: function(){
            console.log( '开电脑' );
        }
    };
    // 登陆QQ的命令
    var openQQCommand = {
        execute: function(){
            console.log( '登录QQ' );
        }
    };
    
    // 创建一个宏命令
    var MacroCommand = function(){
        return {
            // 宏命令的子命令列表
            commandsList: [],
            // 添加命令到子命令列表
            add: function( command ){
                this.commandsList.push( command );
            },
            // 依次执行子命令列表里面的命令
            execute: function(){
                for ( var i = 0, command; command = this.commandsList[ i++ ]; ){
                    command.execute();
                }
            }
        }
    };
    
    var macroCommand = MacroCommand();
    macroCommand.add( closeDoorCommand );
    macroCommand.add( openPcCommand );
    macroCommand.add( openQQCommand );
    macroCommand.execute();
    
    

    通过观察这段代码,我们很容易发现,宏命令中包含了一组子命令,它们组成了一个树形结构,这里是一棵结构非常简单的树

    其中,marcoCommand被称为组合对象,closeDoorCommand、openPcCommand、openQQCommand都是叶对象。在macroCommand的execute方法里,并不执行真正的操作,而是遍历它所包含的叶对象,把真正的execute请求委托给这些叶对象。

    macroCommand表现得像一个命令,但它实际上只是一组真正命令的“代理”。并非真正的代理,虽然结构上相似,但macroCommand只负责传递请求给叶对象,它的目的不在于控制对叶对象的访问。

    组合模式的用途

    组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构。除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性,下面分别说明。

    • 表示树形结构。通过回顾上面的例子,我们很容易找到组合模式的一个优点:提供了一种遍历树形结构的方案,通过调用组合对象的execute方法,程序会递归调用组合对象下面的叶对象的execute方法,所以我们的万能遥控器只需要一次操作,便能依次完成关
      门、打开电脑、登录QQ这几件事情。组合模式可以非常方便地描述对象部分-整体层次结构。
    • 利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现,可以使客户端忽略组合对象和单个对象的不同。在组合模式中,客户将统一地使用组合结构中的所有对象,而不需要关心它究竟是组合对象还是单个对象。

    这在实际开发中会给客户带来相当大的便利性,当我们往万能遥控器里面添加一个命令的时候,并不关心这个命令是宏命令还是普通子命令。这点对于我们不重要,我们只需要确定它是一个命令,并且这个命令拥有可执行的execute方法,那么这个命令就可以被添加进万能遥控器。

    当宏命令和普通子命令接收到执行execute方法的请求时,宏命令和普通子命令都会做它们各自认为正确的事情。这些差异是隐藏在客户背后的,在客户看来,这种透明性可以让我们非常自由地扩展这个万能遥控器。

    请求在树中传递的过程

    在组合模式中,请求在树中传递的过程总是遵循一种逻辑。

    以宏命令为例,请求从树最顶端的对象往下传递,如果当前处理请求的对象是叶对象(普通子命令),叶对象自身会对请求作出相应的处理;如果当前处理请求的对象是组合对象(宏命令),组合对象则会遍历它属下的子节点,将请求继续传递给这些子节点。

    总而言之,如果子节点是叶对象,叶对象自身会处理这个请求,而如果子节点还是组合对象,请求会继续往下传递。叶对象下面不会再有其他子节点,一个叶对象就是树的这条枝叶的尽头,组合对象下面可能还会有子节点

    请求从上到下沿着树进行传递,直到树的尽头。作为客户,只需要关心树最顶层的组合对象,客户只需要请求这个组合对象,请求便会沿着树往下传递,依次到达所有的叶对象。

    由于上面这个宏命令和子命令组成的树太过简单,我们还不能清楚地看到组合模式带来的好处,如果只是简单地遍历一组子节点,迭
    代器便能解决所有的问题。接下来我们将创造一个更强大的宏命令,这个宏命令中又包含了另外一些宏命令和普通子命令,看起来是一棵相对较复杂的树。

    更强大的宏命令

    目前的“万能遥控器”,包含了关门、开电脑、登录QQ这3个命令。现在我们需要一个“超级万能遥控器”,可以控制家里所有的电器,这个遥控器拥有以下功能:

    • 打开空调
    • 打开电视和音响
    • 关门、开电脑、登录QQ
    // 创建一个宏命令
    var MacroCommand = function(){
        return {
            // 宏命令的子命令列表
            commandsList: [],
            // 添加命令到子命令列表
            add: function( command ){
                this.commandsList.push( command );
            },
            // 依次执行子命令列表里面的命令
            execute: function(){
                for ( var i = 0, command; command = this.commandsList[ i++ ]; ){
                    command.execute();
                }
            }
        }
    };
    
    <!--打开空调命令-->
    var openAcCommand = {
        execute: function(){
            console.log( '打开空调' );
        }
    };
    
    <!--打开电视和音响-->
    var openTvCommand = {
        execute: function(){
            console.log( '打开电视' );
        }
    };
    var openSoundCommand = {
        execute: function(){
            console.log( '打开音响' );
        }
    };
    //创建一个宏命令
    var macroCommand1 = MacroCommand();
    //把打开电视装进这个宏命令里
    macroCommand1.add(openTvCommand)
    //把打开音响装进这个宏命令里
    macroCommand1.add(openSoundCommand)
    
    <!--关门、打开电脑和打登录QQ的命令-->
    var closeDoorCommand = {
        execute: function(){
            console.log( '关门' );
        }
    };
    var openPcCommand = {
        execute: function(){
            console.log( '开电脑' );
        }
    };
    var openQQCommand = {
        execute: function(){
            console.log( '登录QQ' );
        }
    };
    //创建一个宏命令
    var macroCommand2 = MacroCommand();
    //把关门命令装进这个宏命令里
    macroCommand2.add( closeDoorCommand );
    //把开电脑命令装进这个宏命令里
    macroCommand2.add( openPcCommand );
    //把登录QQ命令装进这个宏命令里
    macroCommand2.add( openQQCommand );
    
    <!--把各宏命令装进一个超级命令中去-->
    var macroCommand = MacroCommand();
    macroCommand.add( openAcCommand );
    macroCommand.add( macroCommand1 );
    macroCommand.add( macroCommand2 );
    

    从以上代码可以看出基本对象可以被组合成更复杂的组合对象,合对象又可以被组合,这样不断递归下去,这棵树的结构可以支持任意多的复杂度。在树最终被构造完成之后,让整颗树最终运转起来的步骤非常简单,只需要调用最上层对象的execute方法。每当对最上层的对象进行一次请求时,实际上是在对整个树进行深度优先的搜索,而创建组合对象的程序员并不关心这些内在的细节,往这棵树里面添加一些新的节点对象是非常容易的事情。

    透明性带来的安全问题

    组合模式的透明性使得发起请求的客户不用去顾忌树中组合对象和叶对象的区别,但它们在本质上有是区别的。

    组合对象可以拥有子节点,叶对象下面就没有子节点,所以我们也许会发生一些误操作,比如试图往叶对象中添加子节点。解决方案通常是给叶对象也增加add方法,并且在调用这个方法时,抛出一个异常来及时提醒客户,

    一些值得注意的地方

    在使用组合模式的时候,还有以下几个值得我们注意的地方。

    1.组合模式不是父子关系

    组合模式的树型结构容易让人误以为组合对象和叶对象是父子关系,这是不正确的。

    2.对叶对象操作的一致性

    组合模式除了要求组合对象和叶对象拥有相同的接口之外,还有一个必要条件,就是对一组叶对象的操作必须具有一致性。

    比如公司要给全体员工发放元旦的过节费1000块,这个场景可以运用组合模式,但如果公司给今天过生日的员工发送一封生日祝福的邮件,组合模式在这里就没有用武之地了,除非先把今天过生日的员工挑选出来。只有用一致的方式对待列表中的每个叶对象的时候,才适合使用组合模式。

    3.双向映射关系

    发放过节费的通知步骤是从公司到各个部门,再到各个小组,最后到每个员工的邮箱里。这本身是一个组合模式的好例子,但要考虑的一种情况是,也许某些员工属于多个组织架构。比如某位架构师既隶属于开发组,又隶属于架构组,对象之间的关系并不是严格意义上的层次结构,在这种情况下,是不适合使用组合模式的,该架构师很可能会收到两份过节费。

    4.用职责链模式提高组合模式性能

    在组合模式中,如果树的结构比较复杂,节点数量很多,在遍历树的过程中,性能方面也许表现得不够理想。有时候我们确实可以借助一些技巧,在实际操作中避免遍历整棵树,有一种现成的方案是借助职责链模式。职责链模式一般需要我们手动去设置链条,但在组合模式中,父对象和子对象之间实际上形成了天然的职责链。让请求顺着链条从父对象往子对象传递,或者是反过来从子对象往父对象传递,直到遇到可以处理该请求的对象为止,这也是职责链模式的经典运用场景之一。

    何时使用组合模式

    组合模式如果运用得当,可以大大简化客户的代码。一般来说,组合模式适用于以下这两种情况。

    • 表示对象的部分-整体层次结构。组合模式可以方便地构造一棵树来表示对象的部分-整体结构。特别是我们在开发期间不确定这棵
      树到底存在多少层次的时候。在树的构造最终完成之后,只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模式
      中增加和删除树的节点非常方便,并且符合开放-封闭原则。

    • 客户希望统一对待树中的所有对象。组合模式使客户可以忽略组合对象和叶对象的区别,客户在面对这棵树的时候,不用关心当前正在处理的对象是组合对象还是叶对象,也就不用写一堆if、else语句来分别处理它们。组合对象和叶对象会各自做自己正确的事
      情,这是组合模式最重要的能力。

    展开全文
  • 设计模式 | 组合模式及典型应用

    千次阅读 多人点赞 2018-10-05 17:36:55
    介绍组合模式 示例 组合模式总结 源码分析组合模式的典型应用 java.awt中的组合模式 Java集合中的组合模式 Mybatis SqlNode中的组合模式 更多内容可访问我的个人博客:http://laijianfeng.org 推荐阅读 设计...

    本文的主要内容:

    • 介绍组合模式
    • 示例
    • 组合模式总结
    • 源码分析组合模式的典型应用
      • java.awt中的组合模式
      • Java集合中的组合模式
      • Mybatis SqlNode中的组合模式

    组合模式

    树形结构不论在生活中或者是开发中都是一种非常常见的结构,一个容器对象(如文件夹)下可以存放多种不同的叶子对象或者容器对象,容器对象与叶子对象之间属性差别可能非常大。

    由于容器对象和叶子对象在功能上的区别,在使用这些对象的代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下我们希望一致地处理它们,因为对于这些对象的区别对待将会使得程序非常复杂。

    一个简化的Linux目录树

    组合模式为解决此类问题而诞生,它可以让叶子对象和容器对象的使用具有一致性

    组合模式(Composite Pattern):组合多个对象形成树形结构以表示具有 “整体—部分” 关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为 “整体—部分”(Part-Whole) 模式,它是一种对象结构型模式。

    由于在软件开发中存在大量的树形结构,因此组合模式是一种使用频率较高的结构型设计模式,Java SE中的AWT和Swing包的设计就基于组合模式。

    除此以外,在XML解析、组织结构树处理、文件系统设计等领域,组合模式都得到了广泛应用。

    角色

    Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。

    Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。

    Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。

    组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。

    示例

    我们来实现一个简单的目录树,有文件夹和文件两种类型,首先需要一个抽象构件类,声明了文件夹类和文件类需要的方法

    public abstract class Component {
    
        public String getName() {
            throw new UnsupportedOperationException("不支持获取名称操作");
        }
    
        public void add(Component component) {
            throw new UnsupportedOperationException("不支持添加操作");
        }
    
        public void remove(Component component) {
            throw new UnsupportedOperationException("不支持删除操作");
        }
    
        public void print() {
            throw new UnsupportedOperationException("不支持打印操作");
        }
    
        public String getContent() {
            throw new UnsupportedOperationException("不支持获取内容操作");
        }
    }
    

    实现一个文件夹类 Folder,继承 Component,定义一个 List<Component> 类型的componentList属性,用来存储该文件夹下的文件和子文件夹,并实现 getName、add、remove、print等方法

    public class Folder extends Component {
        private String name;
        private List<Component> componentList = new ArrayList<Component>();
    
        public Folder(String name) {
            this.name = name;
        }
    
        @Override
        public String getName() {
            return this.name;
        }
    
        @Override
        public void add(Component component) {
            this.componentList.add(component);
        }
    
        @Override
        public void remove(Component component) {
            this.componentList.remove(component);
        }
    
        @Override
        public void print() {
            System.out.println(this.getName());
            for (Component component : this.componentList) {
                component.print();
            }
        }
    }
    

    文件类 File,继承Component父类,实现 getName、print、getContent等方法

    public class File extends Component {
        private String name;
        private String content;
    
        public File(String name, String content) {
            this.name = name;
            this.content = content;
        }
    
        @Override
        public String getName() {
            return this.name;
        }
    
        @Override
        public void print() {
            System.out.println(this.getName());
        }
    
        @Override
        public String getContent() {
            return this.content;
        }
    }
    

    我们来测试一下

    public class Test {
        public static void main(String[] args) {
            Folder DSFolder = new Folder("设计模式资料");
            File note1 = new File("组合模式笔记.md", "组合模式组合多个对象形成树形结构以表示具有 \"整体—部分\" 关系的层次结构");
            File note2 = new File("工厂方法模式.md", "工厂方法模式定义一个用于创建对象的接口,让子类决定将哪一个类实例化。");
            DSFolder.add(note1);
            DSFolder.add(note2);
    
            Folder codeFolder = new Folder("样例代码");
            File readme = new File("README.md", "# 设计模式示例代码项目");
            Folder srcFolder = new Folder("src");
            File code1 = new File("组合模式示例.java", "这是组合模式的示例代码");
    
            srcFolder.add(code1);
            codeFolder.add(readme);
            codeFolder.add(srcFolder);
            DSFolder.add(codeFolder);
    
            DSFolder.print();
        }
    }
    

    输出结果

    设计模式资料
    组合模式笔记.md
    工厂方法模式.md
    样例代码
    README.md
    src
    组合模式示例.java
    

    输出正常,不过有个小问题,从输出看不出它们的层级结构,为了体现出它们之间的层级关系,我们需要改造一下 Folder 类,增加一个 level 属性,并修改 print 方法

    public class Folder extends Component {
        private String name;
        private List<Component> componentList = new ArrayList<Component>();
        public Integer level;
    
        public Folder(String name) {
            this.name = name;
        }
    
        @Override
        public String getName() {
            return this.name;
        }
    
        @Override
        public void add(Component component) {
            this.componentList.add(component);
        }
    
        @Override
        public void remove(Component component) {
            this.componentList.remove(component);
        }
    
        @Override
        public void print() {
            System.out.println(this.getName());
            if (this.level == null) {
                this.level = 1;
            }
            String prefix = "";
            for (int i = 0; i < this.level; i++) {
                prefix += "\t- ";
            }
            for (Component component : this.componentList) {
                if (component instanceof Folder){
                    ((Folder)component).level = this.level + 1;
                }
                System.out.print(prefix);
                component.print();
            }
            this.level = null;
        }
    }
    

    现在的输出就有相应的层级结构了

    设计模式资料
    	- 组合模式笔记.md
    	- 工厂方法模式.md
    	- 样例代码
    	- 	- README.md
    	- 	- src
    	- 	- 	- 组合模式示例.java
    

    我们可以画出它们之间的类图

    示例.组合模式类图

    在这里父类 Component 是一个抽象构件类,Folder 类是一个容器构件类,File 是一个叶子构件类,Folder 和 File 继承了 Component,Folder 与 Component 又是聚合关系

    透明与安全

    在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安
    全组合模式两种形式。

    透明组合模式

    透明组合模式中,抽象构件角色中声明了所有用于管理成员对象的方法,譬如在示例中 Component 声明了 addremove 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。

    透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)

    安全组合模式

    在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在容器构件 Composite 类中声明并实现这些方法。

    安全组合模式模式图

    安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。

    在实际应用中 java.awtswing 中的组合模式即为安全组合模式。

    组合模式总结

    组合模式的主要优点如下:

    • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
    • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
    • 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
    • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

    组合模式的主要缺点如下:

    • 使得设计更加复杂,客户端需要花更多时间理清类之间的层次关系。
    • 在增加新构件时很难对容器中的构件类型进行限制。

    适用场景

    • 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。
    • 在一个使用面向对象语言开发的系统中需要处理一个树形结构。
    • 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。

    源码分析组合模式的典型应用

    java.awt中的组合模式

    Java GUI分两种:

    • AWT(Abstract Window Toolkit):抽象窗口工具集,是第一代的Java GUI组件。绘制依赖于底层的操作系统。基本的AWT库处理用户界面元素的方法是把这些元素的创建和行为委托给每个目标平台上(Windows、 Unix、 Macintosh等)的本地GUI工具进行处理。

    • Swing,不依赖于底层细节,是轻量级的组件。现在多是基于Swing来开发。

    我们来看一个AWT的简单示例:

    注意:为了正常显示中文,需要在IDEA中的 Edit Configurations -> VM Options 中设置参数 -Dfile.encoding=GB18030

    import java.awt.*;
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;
    
    public class MyFrame extends Frame {
    
        public MyFrame(String title) {
            super(title);
        }
    
        public static void main(String[] args) {
            MyFrame frame = new MyFrame("这是一个 Frame");
    
            // 定义三个构件,添加到Frame中去
            Button button = new Button("按钮 A");
            Label label = new Label("这是一个 AWT Label!");
            TextField textField = new TextField("这是一个 AWT TextField!");
    
            frame.add(button, BorderLayout.EAST);
            frame.add(label, BorderLayout.SOUTH);
            frame.add(textField, BorderLayout.NORTH);
    
            // 定义一个 Panel,在Panel中添加三个构件,然后再把Panel添加到Frame中去
            Panel panel = new Panel();
            panel.setBackground(Color.pink);
    
            Label lable1 = new Label("用户名");
            TextField textField1 = new TextField("请输入用户名:", 20);
            Button button1 = new Button("确定");
            panel.add(lable1);
            panel.add(textField1);
            panel.add(button1);
    
            frame.add(panel, BorderLayout.CENTER);
    
            // 设置Frame的属性
            frame.setSize(500, 300);
            frame.setBackground(Color.orange);
            // 设置点击关闭事件
            frame.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
            frame.setVisible(true);
        }
    }
    

    运行后窗体显示如下

    示例.AWT绘制窗体

    我们在Frame容器中添加了三个不同的构件 ButtonLabelTextField,还添加了一个 Panel 容器,Panel 容器中又添加了 ButtonLabelTextField 三个构件,为什么容器 FramePanel 可以添加类型不同的构件和容器呢?

    我们先来看下AWT Component的类图

    AWT Component类图

    GUI组件根据作用可以分为两种:基本组件和容器组件。

    • 基本组件又称构件,诸如按钮、文本框之类的图形界面元素。
    • 容器是一种比较特殊的组件,可以容纳其他组件,容器如窗口、对话框等。所有的容器类都是 java.awt.Container 的直接或间接子类

    容器父类 Container 的部分代码如下

    public class Container extends Component {
        /**
         * The components in this container.
         * @see #add
         * @see #getComponents
         */
        private java.util.List<Component> component = new ArrayList<>();
        
        public Component add(Component comp) {
            addImpl(comp, null, -1);
            return comp;
        }
        // 省略...
    }
    

    容器父类 Container 内部定义了一个集合用于存储 Component 对象,而容器组件 Container 和 基本组件如 ButtonLabelTextField 等都是 Component 的子类,所以可以很清楚的看到这里应用了组合模式

    Component 类中封装了组件通用的方法和属性,如图形的组件对象、大小、显示位置、前景色和背景色、边界、可见性等,因此许多组件类也就继承了 Component 类的成员方法和成员变量,相应的成员方法包括:

       getComponentAt(int x, int y)
       getFont()
       getForeground()
       getName()
       getSize()
       paint(Graphics g)
       repaint()
       update()
       setVisible(boolean b)
       setSize(Dimension d)
       setName(String name)
    

    Java集合中的组合模式

    HashMap 提供 putAll 的方法,可以将另一个 Map 对象放入自己的存储空间中,如果有相同的 key 值则会覆盖之前的 key 值所对应的 value 值

    public class Test {
        public static void main(String[] args) {
            Map<String, Integer> map1 = new HashMap<String, Integer>();
            map1.put("aa", 1);
            map1.put("bb", 2);
            map1.put("cc", 3);
            System.out.println("map1: " + map1);
    
            Map<String, Integer> map2 = new LinkedMap();
            map2.put("cc", 4);
            map2.put("dd", 5);
            System.out.println("map2: " + map2);
    
            map1.putAll(map2);
            System.out.println("map1.putAll(map2): " + map1);
        }
    }
    

    输出结果

    map1: {aa=1, bb=2, cc=3}
    map2: {cc=4, dd=5}
    map1.putAll(map2): {aa=1, bb=2, cc=4, dd=5}
    

    查看 putAll 源码

        public void putAll(Map<? extends K, ? extends V> m) {
            putMapEntries(m, true);
        }
    

    putAll 接收的参数为父类 Map 类型,所以 HashMap 是一个容器类,Map 的子类为叶子类,当然如果 Map 的其他子类也实现了 putAll 方法,那么它们都既是容器类,又都是叶子类

    同理,ArrayList 中的 addAll(Collection<? extends E> c) 方法也是一个组合模式的应用,在此不做探讨

    Mybatis SqlNode中的组合模式

    MyBatis 的强大特性之一便是它的动态SQL,其通过 if, choose, when, otherwise, trim, where, set, foreach 标签,可组合成非常灵活的SQL语句,从而提高开发人员的效率。

    来几个官方示例:

    动态SQL – IF

    <select id="findActiveBlogLike"  resultType="Blog">
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’ 
      <if test="title != null">
        AND title like #{title}
      </if>
      <if test="author != null and author.name != null">
        AND author_name like #{author.name}
      </if>
    </select>
    

    动态SQL – choose, when, otherwise

    <select id="findActiveBlogLike"  resultType="Blog">
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’
      <choose>
        <when test="title != null">
          AND title like #{title}
        </when>
        <when test="author != null and author.name != null">
          AND author_name like #{author.name}
        </when>
        <otherwise>
          AND featured = 1
        </otherwise>
      </choose>
    </select>
    

    动态SQL – where

    <select id="findActiveBlogLike"  resultType="Blog">
      SELECT * FROM BLOG 
      <where> 
        <if test="state != null">
             state = #{state}
        </if> 
        <if test="title != null">
            AND title like #{title}
        </if>
        <if test="author != null and author.name != null">
            AND author_name like #{author.name}
        </if>
      </where>
    </select>
    

    动态SQL – foreach

    <select id="selectPostIn" resultType="domain.blog.Post">
      SELECT * FROM POST P WHERE ID in
      <foreach item="item" index="index" collection="list"
          open="(" separator="," close=")">
            #{item}
      </foreach>
    </select>
    

    Mybatis在处理动态SQL节点时,应用到了组合设计模式,Mybatis会将映射配置文件中定义的动态SQL节点、文本节点等解析成对应的 SqlNode 实现,并形成树形结构。

    SQLNode 的类图如下所示

    Mybatis SqlNode 类图

    需要先了解 DynamicContext 类的作用:主要用于记录解析动态SQL语句之后产生的SQL语句片段,可以认为它是一个用于记录动态SQL语句解析结果的容器

    抽象构件为 SqlNode 接口,源码如下

    public interface SqlNode {
      boolean apply(DynamicContext context);
    }
    

    applySQLNode 接口中定义的唯一方法,该方法会根据用户传入的实参,参数解析该SQLNode所记录的动态SQL节点,并调用 DynamicContext.appendSql() 方法将解析后的SQL片段追加到 DynamicContext.sqlBuilder 中保存,当SQL节点下所有的 SqlNode 完成解析后,我们就可以从 DynamicContext 中获取一条动态生产的、完整的SQL语句

    然后来看 MixedSqlNode 类的源码

    public class MixedSqlNode implements SqlNode {
      private List<SqlNode> contents;
    
      public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
      }
    
      @Override
      public boolean apply(DynamicContext context) {
        for (SqlNode sqlNode : contents) {
          sqlNode.apply(context);
        }
        return true;
      }
    }
    

    MixedSqlNode 维护了一个 List<SqlNode> 类型的列表,用于存储 SqlNode 对象,apply 方法通过 for循环 遍历 contents 并调用其中对象的 apply 方法,这里跟我们的示例中的 Folder 类中的 print 方法非常类似,很明显 MixedSqlNode 扮演了容器构件角色

    对于其他SqlNode子类的功能,稍微概括如下:

    • TextSqlNode:表示包含 ${} 占位符的动态SQL节点,其 apply 方法会使用 GenericTokenParser 解析 ${} 占位符,并直接替换成用户给定的实际参数值
    • IfSqlNode:对应的是动态SQL节点 <If> 节点,其 apply 方法首先通过 ExpressionEvaluator.evaluateBoolean() 方法检测其 test 表达式是否为 true,然后根据 test 表达式的结果,决定是否执行其子节点的 apply() 方法
    • TrimSqlNode :会根据子节点的解析结果,添加或删除相应的前缀或后缀。
    • WhereSqlNodeSetSqlNode 都继承了 TrimSqlNode
    • ForeachSqlNode:对应 <foreach> 标签,对集合进行迭代
    • 动态SQL中的 <choose><when><otherwise> 分别解析成 ChooseSqlNodeIfSqlNodeMixedSqlNode

    综上,SqlNode 接口有多个实现类,每个实现类对应一个动态SQL节点,其中 SqlNode 扮演抽象构件角色,MixedSqlNode 扮演容器构件角色,其它一般是叶子构件角色

    参考:
    刘伟:设计模式Java版
    慕课网java设计模式精讲 Debug 方式+内存分析
    Java AWT基础及布局管理
    【java源码一带一路系列】之HashMap.putAll()
    徐郡明:Mybatis技术内幕 3.2 SqlNode&SqlSource
    Mybatis 3.4.7 文档:动态 SQL

    更多内容可访问我的个人博客:http://laijianfeng.org
    关注【小旋锋】微信公众号

    推荐阅读

    设计模式 | 简单工厂模式及典型应用
    设计模式 | 工厂方法模式及典型应用
    设计模式 | 抽象工厂模式及典型应用
    设计模式 | 建造者模式及典型应用
    设计模式 | 原型模式及典型应用
    设计模式 | 外观模式及典型应用
    设计模式 | 装饰者模式及典型应用
    设计模式 | 适配器模式及典型应用
    设计模式 | 享元模式及典型应用

    展开全文
  • 组合模式分析前言总结 前言 上一篇,我们介绍了建造者模式,以及建造者模式在源码中的运用,今天我们会先介绍一下责任链模式,然后会再通过一个示例来将责任链模式和建造者模式结合起来应用。 总结 本文介绍了责任链...

    设计模式系列总览

    设计模式飞机票
    三大工厂模式登机入口
    策略模式登机入口
    委派模式登机入口
    模板方法模式登机入口
    观察者模式登机入口
    单例模式登机入口
    原型模式登机入口
    代理模式登机入口
    装饰者模式登机入口
    适配器模式登机入口
    建造者模式登机入口
    责任链模式登机入口
    享元模式登机入口
    组合模式登机入口
    门面模式登机入口
    桥接模式登机入口
    中介者模式登机入口
    迭代器模式登机入口
    状态模式登机入口
    解释器模式登机入口
    备忘录模式登机入口
    命令模式登机入口
    访问者模式登机入口
    软件设计7大原则和设计模式总结登机入口

    前言

    本文主要会讲述组合模式的用法,并会结合在JDK和MyBatis源码中的运用来进一步理解组合模式。

    在编码原则中,有一条是:多用组合,少用继承。当然这里的组合和我们今天要讲的组合模式并不等价,这里的组合其实就是一种聚合,那么聚合和组合有什么区别呢?

    组合和聚合

    人在一起叫团伙,心在一起叫团队。用这句话来诠释组合与聚合的区别是相对恰当的。

    聚合就是说各个对象聚合在一起工作,但是我没有你也行,我照样可以正常运行。但是组合呢,关系就比较密切,组合中的各个对象之间组成了一个整体,缺少了某一个对象就不能正常运行或者说功能会有很大缺陷。
    也就是说聚合对象不具备相同生命周期,而组合的对象具有相同的生命周期

    举个例子:
    比如说电脑和U盘就是聚合,而电脑显示器和主机就是组合。

    什么是组合模式

    组合模式(Composite Pattern)也称之为整体-部分(Part-Whole)模式。组合模式的核心是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得单个对象和组合对象的使用具有一致性。组合模式属于结构型模式。

    组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,最顶层的节点称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点如下图所示:
    在这里插入图片描述
    讲了这么多,感觉有点抽象,所以依然是老规矩:Talk is cheap,Show you the code

    示例

    组合模式有两种写法,分别是透明模式安全模式。下面我们就以高考的科目为例来看看组合模式是如何体现在代码中的

    透明组合模式

    1、首先建立一个顶层的抽象科目类,这个类中定义了三个通用操作方法,但是均默认不支持操作

    package com.zwx.design.pattern.composite.transparency;
    
    /**
     * 顶层抽象组件
     */
    public abstract class GkAbstractCourse {
        public void addChild(GkAbstractCourse course){
            System.out.println("不支持添加操作");
        }
    
        public String getName() throws Exception {
            throw new Exception("不支持获取名称");
        }
    
        public void info() throws Exception{
            throw new Exception("不支持查询信息操作");
        }
    }
    

    PS:这个类是抽象类,但是在这里并没有将这些方法定义为抽象方法,而是默认都抛出异常。这么做的原因是假如定义为抽象方法,那么所有的子类都必须重写父类方法。但是这种通过抛异常的方式,如果子类需要用到的功能就重写覆盖父类方法即可,不需要用到的方法直接无需重写。

    2、新建一个普通科目类继承通用科目抽象类,这个类作为叶子节点,没有重写addChild方法,也就是这个类属于叶子节点,不支持添加子节点:

    package com.zwx.design.pattern.composite.transparency;
    
    /**
     * 普通科目类(叶子节点)
     */
    public class CommonCource extends GkAbstractCourse {
        private String name;//课程名称
        private String score;//课程分数
    
        public CommonCource(String name, String score) {
            this.name = name;
            this.score = score;
        }
    
        @Override
        public String getName(){
            return this.name;
        }
    
        @Override
        public void info() {
            System.out.println("课程:" + this.name + ",分数:" + score);
        }
    }
    

    3、建立一个具有层级的节点,三个方法都重写了,支持添加子节点,这个类里面为了方便打印的时候看出层级关系,所以我定义了一个层级属性。

    package com.zwx.design.pattern.composite.transparency;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 树枝节点
     */
    public class LevelCource extends GkAbstractCourse{
        private List<GkAbstractCourse> courseList = new ArrayList<>();
        private String name;
        private int level;
    
        public LevelCource(String name, int level) {
            this.name = name;
            this.level = level;
        }
    
        @Override
        public void addChild(GkAbstractCourse course) {
            courseList.add(course);
        }
    
        @Override
        public String getName(){
            return this.name;
        }
    
        @Override
        public void info() throws Exception {
           System.out.println("课程:" + this.name);
           for (GkAbstractCourse course : courseList){
               for (int i=0;i<level;i++){
                   System.out.print("  ");
               }
               System.out.print(">");
               course.info();
           }
        }
    }
    

    4、建立一个测试类来测试一下:

    package com.zwx.design.pattern.composite.transparency;
    
    public class TestTransparency {
        public static void main(String[] args) throws Exception {
            GkAbstractCourse ywCourse = new CommonCource("语文","150");
            GkAbstractCourse sxCourse = new CommonCource("数学","150");
            GkAbstractCourse yyCourse = new CommonCource("英语","150");
    
            GkAbstractCourse wlCourse = new CommonCource("物理","110");
            GkAbstractCourse hxCourse = new CommonCource("化学","100");
            GkAbstractCourse swCourse = new CommonCource("生物","90");
    
            GkAbstractCourse lzCourse = new LevelCource("理综",2);
            lzCourse.addChild(wlCourse);
            lzCourse.addChild(hxCourse);
            lzCourse.addChild(swCourse);
    
            GkAbstractCourse gkCourse = new LevelCource("理科高考科目",1);
            gkCourse.addChild(ywCourse);
            gkCourse.addChild(sxCourse);
            gkCourse.addChild(yyCourse);
            gkCourse.addChild(lzCourse);
            
            gkCourse.info();
        }
    }
    

    输出结果:

    课程:理科高考科目
      >课程:语文,分数:150
      >课程:数学,分数:150
      >课程:英语,分数:150
      >课程:理综
        >课程:物理,分数:110
        >课程:化学,分数:100
        >课程:生物,分数:90
    

    这里如果用普通科目去调用add方法就会抛出异常,假如上面调用:

     swCourse.addChild(ywCourse);
    

    会输出

    不支持添加操作
    

    因为在普通科目类里面并没有重写addChild方法。

    透明组合模式的缺陷

    透明模式的特点就是将组合对象所有的公共方法都定义在了抽象组件内,这样做的好处是客户端无需分辨当前对象是属于树枝节点还是叶子节点,因为它们具备了完全一致的接口,不过缺点就是叶子节点得到到了一些不属于它的方法,比如上面的addChild方法,这违背了接口隔离性原则

    安全组合模式

    安全组合模式只是规定了系统各个层次的最基础的一致性行为,而把组合(树节点)本身的方法(如树枝节点管理子类的addChild等方法)放到自身当中。

    1、首先还是建立一个顶层的抽象根节点(这里面只定义了一个通用的抽象info方法):

    package com.zwx.design.pattern.composite.safe;
    
    package com.zwx.design.pattern.composite.safe;
    
    /**
     * 顶层抽象组件
     */
    public abstract class GkAbstractCourse {
        protected String name;
        protected String score;
    
        public GkAbstractCourse(String name, String score) {
            this.name = name;
            this.score = score;
        }
    
        public abstract void  info();
    }
    

    2、建立一个叶子节点(这里只是重写了info方法,没有定义其他特有方法):

    package com.zwx.design.pattern.composite.safe;
    
    /**
     * 叶子节点
     */
    public class CommonCource extends GkAbstractCourse {
    
        public CommonCource(String name,String score) {
            super(name,score);
        }
    
        @Override
        public void info() {
            System.out.println("课程:" + this.name + ",分数:" + this.score);
        }
    }
    

    3、定义一个树枝节点(这个类当中定义了一个树枝特有的方法addChild):

    package com.zwx.design.pattern.composite.safe;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 树枝节点
     */
    public class LevelCource extends GkAbstractCourse{
        private List<GkAbstractCourse> courseList = new ArrayList<>();
        private int level;
    
        public LevelCource(String name, String score,int level) {
            super(name,score);
            this.level = level;
        }
    
        public void addChild(GkAbstractCourse course) {
            courseList.add(course);
        }
    
        @Override
        public void info() {
           System.out.println("课程:" + this.name + ",分数:" + this.score);
           for (GkAbstractCourse course : courseList){
               for (int i=0;i<level;i++){
                   System.out.print("  ");
               }
               System.out.print(">");
               course.info();
           }
        }
    }
    

    4、新建测试类来测试:

    package com.zwx.design.pattern.composite.safe;
    
    public class TestSafe {
        public static void main(String[] args) throws Exception {
            CommonCource ywCourse = new CommonCource("语文","150");
            CommonCource sxCourse = new CommonCource("数学","150");
            CommonCource yyCourse = new CommonCource("英语","150");
    
            CommonCource wlCourse = new CommonCource("物理","110");
            CommonCource hxCourse = new CommonCource("化学","100");
            CommonCource swCourse = new CommonCource("生物","90");
    
            LevelCource lzCourse = new LevelCource("理综","300",2);
            lzCourse.addChild(wlCourse);
            lzCourse.addChild(hxCourse);
            lzCourse.addChild(swCourse);
    
            LevelCource gkCourse = new LevelCource("理科高考","750",1);
            gkCourse.addChild(ywCourse);
            gkCourse.addChild(sxCourse);
            gkCourse.addChild(yyCourse);
    
            gkCourse.addChild(lzCourse);
            gkCourse.info();
        }
    }
    

    输出结果为:

    课程:理科高考,分数:750
      >课程:语文,分数:150
      >课程:数学,分数:150
      >课程:英语,分数:150
      >课程:理综,分数:300
        >课程:物理,分数:110
        >课程:化学,分数:100
        >课程:生物,分数:90
    

    这里和透明方式不一样,叶子节点不具备addChild功能,所以无法调用,而上面的示例中时可以被调用,但是调用之后显示不支持,这就是这两种写法最大的区别。

    组合模式角色

    从上面示例中,可以看到组合模式包含了以下三个角色:

    • 抽象根节点(Component):定义系统各层次对象的公有属性和方法,可以预先定义一些默认行为和属性。
    • 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
    • 叶子节点(Leaf):是系统遍历层次中的最小单位,下面没有子节点。

    组合模式在JDK源码中的体现

    • 1、HashMap

    HashMap中有一个putAll方法,参数是一个Map,这就是一种组合模式的体现:在这里插入图片描述
    另外还有ArrayList中的addAll方法也是一样。

    • 2、MyBatis中有一个SqlNode接口,下面很多一级标签:在这里插入图片描述
      然后一级标签下面又有二级标签(这就是组合模式的体现):
      在这里插入图片描述

    组合模式应用场景

    组合模式一般应用在有层级关系的场景,最经典的就是树形菜单,文件和文件夹的管理等

    组合模式优缺点

    优点:清楚的定义了分层次的复杂对象,让客户端可以忽略层次的差异,方便对整个层次进行动态控制。
    缺点:其叶子和树枝的声明是实现类而不是接口,违反了依赖倒置原则,而且组合模式会使设计更加抽象不好理解。

    总结

    本文主要介绍了组合模式,并介绍了普通的聚合和组合之间的区别,并通过例子详细解释了组合模式中的透明写法和安全写法的区别,最后结合在JDK和MyBatis源码中的运用来进一步理解组合模式的运用。
    请关注我,和孤狼一起学习进步

    展开全文
  • 从剧情中思考组合模式 组合模式的模型抽象 类图 模型说明 组合模式的优点 组合模式的缺点 应用场景 【故事剧情】 Tony 用的笔记本电脑还是大学时候买的,到现在已经用了5年,虽然后面加过一次内存,也...
  • 本文实例讲述了Python设计模式之组合模式原理与用法。分享给大家供大家参考,具体如下: 组合模式(Composite Pattern):将对象组合成成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象...
  • 组合模式的定义:又叫作整体-部分(Part-Whole)模式,通过将单个对象(叶节点)和组合对象用相同的接口表示,使客户端对单个对象和组合对象的访问具有一致性。它是一种将对象组合成树状的层次结构的模式。属于结构...
  • JAVA设计模式--组合模式

    千次阅读 2019-02-28 08:49:36
    一、什么是组合模式 组合(Composite)模式是一种对象的行为模式。将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。 组合模式的本质:统一叶子对象和组合...
  • Java 设计模式——组合模式

    万次阅读 多人点赞 2016-05-17 17:39:45
    对于组合模式息息相关的另一种模式——迭代模式,它在组合模式中可以说有着重要的地位。在上面的代码中,有点编程逻辑的人应该都可以发现,它们的 print() 方法是对象内部的操作。也就是说,如果我想要通过一个 Menu...
  • 主要介绍了C++设计模式之组合模式,本文讲解什么是组合模式组合模式的优点、组合模式实例等内容,需要的朋友可以参考下
  • 设计模式-组合模式(双十一组合折扣你看懂了吗)

    千次阅读 多人点赞 2020-12-17 09:58:17
    文章目录组合模式双十一折扣策略 组合模式 组合模式(Composite Pattern) 双十一折扣策略 图片摘自网络(papi酱),侵删 以双十一的折扣策略为例讲解,有VIP折扣、活动折扣、满减、店铺优惠券、红包······,...
  • JAVA组合模式

    万次阅读 多人点赞 2018-08-24 00:11:06
    JAVA组合模式 通过本文将学习到 Table of Contents 1、组合模式的概念 2、组合模式的UML图 3、组合模式的实现 4、组合模式的优缺点 5、组合模式适用场景 6、总结 7、关于最近一些事。   红色代表重点,...
  • 好,下面我将结合一些实例,说一下我对组合模式以及观察者模式的了解: 1、组合模式组合模式在对象间形成树形结构; 组合模式中基本对象和组合对象被一致对待; 无须关心对象有多少层, 调用时只需在根部进行调用; ...
  • 组合模式的安全模式与透明模式

    千次阅读 2018-04-14 23:18:00
    转载自 树形结构的处理——组合模式(四) 1 透明组合模式与安全组合模式  通过引入组合模式,Sunny公司设计的杀毒软件具有良好的可扩展性,在增加新的文件类型时,无须修改现有类库代码,只需增加一个...
  • js之组合模式

    千次阅读 2016-12-03 20:36:06
    在命令模式学习完的基础上:更强大的宏命令<!DOCTYPE html> <title>Title 按我 var MacroCommand
  • Java 设计模式(九):组合模式

    千次阅读 2018-12-15 10:31:10
    参考链接:组合模式-Composite Pattern 树形结构在软件中随处可见,例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组织结构等等,如何运用面向对象的方式来处理这种树形结构是组合模式需要解决的...
  • Java设计模式—组合模式

    千次阅读 2016-09-10 19:31:22
    ... 目录 ...组合模式 个人理解: 定义如下: 通用类图如下: 组合模式的包含角色: ● Component 抽象构件角色 ● Leaf 叶子构件 ● Composite 树枝构件 通用源代码: 组合模式的优点: ...
  • 组合模式详解 (附java语言源码)

    千次阅读 2019-06-11 15:09:08
    组合模式(Composite Pattern): 将对象组合成树形结构以表示“部分-整体”的层次结构。Composite 使得客户对单个对象和复合对象的使用具有一致性。(Compose object into tree structures torepresent part-whole...
  • 10、桥接模式(Bridge) 桥接的用意是:将抽象与实现解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫...
  • 惯例我们先来看一看组合模式的定义:组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。通俗的来说,就是讲一系列的对象组合在一个整体...
  • C++ 组合模式

    千次阅读 热门讨论 2017-05-05 20:01:41
    组合模式对单个对象(即:叶子构件)和组合对象(即:容器构件)的使用具有一致性,组合模式又被称为“整体-部分”(Part-Whole)模式,属于对象结构型模式。 简述 模式结构 透明组合模式和安全组合模式 优缺点 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 737,495
精华内容 294,998
关键字:

组合模式