zoukankan      html  css  js  c++  java
  • 组合模式

    组合模式

    案例

    我们想开发一个界面控件库,界面控件分为两大类,一类是单元控件,例如按钮、文本框等,一类是容器控件,例如面板。面板界面内可以放入单元控件和其他面板。这样最终得到一个类似窗体的样子。下面就用代码模拟这一过程。

    1.首先定义一个面板类:

    /**
     * 面板内,可以添加按钮、文本框和其他的面板
     */
    public class Panel {
        private String name;
        // 存放面板的容器
        private List<Panel> panelList = new ArrayList<>();
        // 存放按钮的容器
        private List<Button> buttonList = new ArrayList<>();
        // 存放文本框的容器
        private List<TextBox> textBoxList = new ArrayList<>();
    
        public Panel(String name) {
            this.name = name;
        }
    
        // 三个添加面板、按钮和文本框的方法
        public void addPanel(Panel panel) {
            panelList.add(panel);
        }
    
        public void addButton(Button button) {
            buttonList.add(button);
        }
    
        public void addTextBox(TextBox textBox) {
            textBoxList.add(textBox);
        }
    
        // 分别调用展示面板、按钮和文本框的方法
        public void show(String prefix) {
            System.out.println(prefix + "展示面板[" + this.name + "]");
            for (Button button : buttonList) {
                button.show(prefix + "--");
            }
            for (TextBox textBox : textBoxList) {
                textBox.show(prefix + "--");
            }
            for (Panel panel : panelList) {
                panel.show(prefix + "--");
            }
        }
    }
    

    2.定义按钮组件

    /**
     * 按钮组件
     */
    public class Button {
        private String name;
    
        public Button(String name) {
            this.name = name;
        }
    
        public void show(String prefix) {
            System.out.println(prefix + "展示按钮[" + this.name + "]");
        }
    }
    

    3.定义文本框组件

    /**
     * 文本框组件
     */
    public class TextBox {
        private String name;
    
        public TextBox(String name) {
            this.name = name;
        }
    
        public void show(String prefix) {
            System.out.println(prefix + "展示文本框[" + this.name + "]");
        }
    }
    

    4.测试在面板上添加按钮、文本框和其他面板:

    public class Main {
        public static void main(String[] args) {
            // 面板 A
            Panel panelA = new Panel("A");
            // 面板 A 放入了一个按钮
            panelA.addButton(new Button("A-1"));
            // 面板 A 放入了一个文本框
            panelA.addTextBox(new TextBox("A-2"));
            // 面板 A 放入了另一个面板 B
            Panel panelB = new Panel("A-B");
            // 面板 B 放入了另一个按钮
            panelB.addButton(new Button("A-B-1"));
            // 面板 B 放入了另一个文本框
            panelB.addTextBox(new TextBox("A-B-2"));
            panelA.addPanel(panelB);
            // 展示面板 A 的内容
            panelA.show("");
        }
    }
    

    5.测试结果:

    展示面板[A]
    --展示按钮[A-1]
    --展示文本框[A-2]
    --展示面板[A-B]
    ----展示按钮[A-B-1]
    ----展示文本框[A-B-2]
    

    以上代码,就结果的结构来看与上面的要求是满足的。但是这一编码设计不够灵活,可扩展性也很差。比如我们新增一个密码框组件,我们除了需要新增一个类以外,还需要修改现有的代码,在Panel类中增加对其类的列表维护,还要修改show()方法中的内容。而且Panel类的设计由于需要定义多个集合存储不同的类型的成员并对其成员,本身就比较复杂了。下面就通过使用组合模式对这写问题进行改善。

    模式介绍

    组合模式(结构型模式),将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。掌握组合模式的重点是要理解清楚 “部分/整体” 还有 ”单个对象“ 与 "组合对象" 的含义。

    从上面的定义中可以看出,组合模式区分出单个对象与组合对象来表示部分与整体的关系。从我们的案例来说,其中 Panel就可以看作是组合对象,而ButtonTextBox类就可以看作是单个对象。

    角色构成:

    • Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
    • Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。
    • Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。

    从角色构成上可以看出组合模式,通过引入抽象构件类Component,同时使用容器构件类Composite和叶子构件类Leaf,使得客户端只需针对Component类进行编码。

    UML类图:

    composite

    从图中我们可以看出,组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,使得客户端可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器。

    那么在最开始的案例中,我们的Panel类就可以看作是一个容器类,而ButtonTextBox类就可以看作是叶子节点。在这之前还需要引入一个Component抽象构件类,下面就根据这一思路对代码进行改造。

    代码改造

    1.首先引入Component抽象构件类:

    /**
     * 抽象构件类角色
     */
    public abstract class Component {
        // 添加成员
        public abstract void add(Component c);
        // 不同的实现类实现不同的展示方式
        public abstract void show(String prefix);
    }
    

    2.容器构件类Panel

    /**
     * 容器构件类角色
     */
    public class Panel extends Component {
        private String name;
        private List<Component> list = new ArrayList<>();
    
        public Panel(String name) {
            this.name = name;
        }
    
        @Override
        public void add(Component c) {
            list.add(c);
        }
    
        @Override
        public void show(String prefix) {
            System.out.println(prefix + "展示面板[" + this.name + "]");
            for (Component component : list) {
                component.show(prefix + "--");
            }
        }
    }
    

    3.两个叶子构建类:

    按钮组件:

    /**
     * 叶子构件类:按钮组件
     */
    public class Button extends Component {
        private String name;
    
        public Button(String name) {
            this.name = name;
        }
    
        @Override
        public void add(Component c) {
            // 这里通过抛异常的方式,拒绝添加子构件
            throw new UnsupportedOperationException();
        }
    
        public void show(String prefix) {
            System.out.println(prefix + "展示按钮[" + this.name + "]");
        }
    }
    

    文本框组件:

    /**
     * 叶子构件类:文本框组件
     */
    public class TextBox extends Component {
        private String name;
    
        public TextBox(String name) {
            this.name = name;
        }
    
        @Override
        public void add(Component c) {
            // 这里通过抛异常的方式,拒绝添加子构件
            throw new UnsupportedOperationException();
        }
    
        public void show(String prefix) {
            System.out.println(prefix + "展示文本框[" + this.name + "]");
        }
    }
    

    4.测试类:

    public class Main {
        // 这里我们只用针对抽象类 Component 编程
        public static void main(String[] args) {
            // 面板 A
            Component panelA = new Panel("A");
            // 面板 A 放入了一个按钮
            panelA.add(new Button("A-1"));
            // 面板 A 放入了一个文本框
            panelA.add(new TextBox("A-2"));
            // 面板 A 放入了另一个面板 B
            Component panelB = new Panel("A-B");
            // 面板 B 放入了另一个按钮
            panelB.add(new Button("A-B-1"));
            // 面板 B 放入了另一个文本框
            panelB.add(new TextBox("A-B-2"));
            panelA.add(panelB);
            // 展示面板 A 的内容
            panelA.show("");
        }
    }
    

    5.测试结果:

    展示面板[A]
    --展示按钮[A-1]
    --展示文本框[A-2]
    --展示面板[A-B]
    ----展示按钮[A-B-1]
    ----展示文本框[A-B-2]
    

    测试结果与上面最开始的测试结果是一模一样的,但是我们通过引入了Component抽象类,使得客户端只用针对Component类进行编程。同时我们在添加新的叶子构件,如一个密码框时,只需要继承Component类就可以达到扩展的目的,符合“开闭原则”。

    模式应用

    在我们使用 Java 来开发界面应用时使用到的java.swing.*包下面的类中就存在组合模式的应用。先来看一段简单的创建窗口的代码。

    ​ 1.创建一个窗体:

    public class Main {
        public static void main(String[] args) {
            // 创建 JFrame 实例
            JFrame jf = new JFrame();
            // 设置宽高
            jf.setSize(200, 100);
            // 设置在窗口中间打开
            jf.setLocationRelativeTo(null);
            // 设置默认关闭操作
            jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    
            // 创建面板,类似于 html 中的 div
            JPanel panel = new JPanel();
    
            // 创建一个输入框
            JTextField textField = new JTextField(8);
            // 添加到面板中
            panel.add(textField);
    
            // 创建一个按钮
            JButton btn = new JButton("提交");
            // 添加到面板中
            panel.add(btn);
    
            // 添加面板到 JFrame 中
            jf.add(panel);
            // 设置界面可见
            jf.setVisible(true);
        }
    }
    

    2.运行结果:

    composite-JFrame

    在窗体上,先创建了一个JPanel面板,然后创建并添加了一个JTextField输入框和一个JButton按钮,最后把面板放入到JFrame中。为什么说这里用到了组合模式呢?下面它们之间的UML类图。

    composite-swing

    从类中可以看到抽象类Component就是组合模式中的抽象构件,JFrameJPanel类作为容器构件角色,而JButtonJTextField类作为叶子构件。这样的使用时容器构件中可以容纳其他容器构件,如代码中的jf.add(panel);。同时也可以在容器构件中添加叶子构件如panel.add(textField);panel.add(btn);

    总结

    1.主要优点

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

    2.主要缺点

    • 在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。

    3.适用场景

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

    参考资料

    本篇文章github代码地址:https://github.com/Phoegel/design-pattern/tree/main/composite
    转载请说明出处,本篇博客地址:https://www.cnblogs.com/phoegel/p/13955969.html

  • 相关阅读:
    Call KernelIoControl in user space in WINCE6.0
    HOW TO:手工删除OCS在AD中的池和其他属性
    关于新版Windows Server 2003 Administration Tools Pack
    关于SQL2008更新一则
    微软发布3款SQL INJECTION攻击检测工具
    HyperV RTM!
    OCS 2007 聊天记录查看工具 OCSMessage
    CoreConfigurator 图形化的 Server Core 配置管理工具
    OC 2007 ADM 管理模板和Live Meeting 2007 ADM 管理模板发布
    Office Communications Server 2007 R2 即将发布
  • 原文地址:https://www.cnblogs.com/phoegel/p/13955969.html
Copyright © 2011-2022 走看看