zoukankan      html  css  js  c++  java
  • 设计模式详解——组合模式

    前言

    今天我们分享的这个设计模式,用一句话来概括的话,就是化零为整,再进一步解释就是,通过这个设计模式,我们可以像操作一个对象一样操作一个对象的集合,不过这个对象在组合模式中被称作叶节点,而对象的集合被称为组合,而这个结合本身也是也节点的树形结构的集合。是不是感觉越来越绕了呢?没关系,下面我就来详细看下组合模式的基本原理和具体实现。

    组合模式

    组合模式允许我们将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

    它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

    使用场景

    • 想表示对象的部分-整体层次结构(树形结构)。

    • 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

    要点

    • 组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象
    • 使用组合模式,我们能把相同的操作应用到组合和个别对象上。换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。

    示例

    下面我们通过一个具体实例来演示下组合模式到底是如何工作的。这里我们直接用了《Head First设计模式》上的示例,是对餐厅菜单的模拟,只不过我引入了一个通用接口。

    组合接口

    首先是组合的接口,这个接口不论是叶节点还是叶节点组合都需要继承,不过都不是直接继承。

    public interface Component {
        void add(Component component);
        void remove(Component component);
        Component getChild(int i);
    }
    
    组合抽象类

    这个抽象类就是给叶节点和节点组合继承的,其中方法都有了默认实现,默认都抛出了UnsupportedOperationException

    public abstract class MenuComponent implements Component {
        @Override
        public void add(Component component) {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public void remove(Component component) {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public Component getChild(int i) {
            throw new UnsupportedOperationException();
        }
    
        public String getName() {
            throw new UnsupportedOperationException();
        }
    
        public String getDescription() {
            throw new UnsupportedOperationException();
        }
    
        public double getPrice() {
            throw new UnsupportedOperationException();
        }
    
        public boolean isVegetarian() {
            throw new UnsupportedOperationException();
        }
    
        public void print() {
            throw new UnsupportedOperationException();
        }
    
    }
    
    叶节点实现

    这里的叶节点主要覆写了父类的getNamegetDescriptionisVegetariangetPrice等方法,这些方法也主要是针对具体菜单的

    public class MenuItem extends MenuComponent {
        private String name;
        private String description;
        private boolean vegetarian;
        private double price;
    
        public MenuItem(String name, String description, boolean vegetarian, double price) {
            this.name = name;
            this.description = description;
            this.vegetarian = vegetarian;
            this.price = price;
        }
    
        @Override
        public String getName() {
            return name;
        }
    
        @Override
        public String getDescription() {
            return description;
        }
    
        @Override
        public boolean isVegetarian() {
            return vegetarian;
        }
    
        @Override
        public double getPrice() {
            return price;
        }
    
        @Override
        public void print() {
            System.out.println("==========start=============");
            System.out.printf("name: %s  price: ¥%s%n", this.getName(), this.getPrice());
            System.out.printf("description: %s  isVegetarian: %s%n", this.getDescription(), this.isVegetarian());
            System.out.println("==========end=============");
        }
    }
    
    节点组合实现

    因为节点组合要管理叶节点,所以这里主要实现了addremovegetChild等方法,当然也实现了getNamegetDescription等基础方法:

    public class Menu extends MenuComponent {
        ArrayList<Component> menuComponents = new ArrayList<>();
        String name;
        String description;
    
        public Menu(String name, String description) {
            this.name = name;
            this.description = description;
        }
    
        @Override
        public void add(Component component) {
            menuComponents.add(component);
        }
    
        @Override
        public void remove(Component component) {
            menuComponents.remove(component);
        }
    
        @Override
        public Component getChild(int i) {
            return menuComponents.get(i);
        }
    
        @Override
        public String getName() {
            return super.getName();
        }
    
        @Override
        public String getDescription() {
            return super.getDescription();
        }
    
        @Override
        public void print() {
            System.out.println("==========start=============");
            System.out.printf("name: %s", this.getName());
            System.out.printf("description: %s", this.getDescription());
            System.out.println("==========child start=============");
            Iterator<Component> iterator = menuComponents.iterator();
            while (iterator.hasNext()) {
                MenuComponent component = (MenuComponent)iterator.next();
                component.print();
            }
            System.out.println("==========child end=============");
            System.out.println("============end===========");
        }
    }
    
    测试代码

    下面我们开始编写测试代码。这里我们分别构造了多个叶节点,并将其中一部分组成节点组合,最后分别执行叶节点和子节点的print方法(这里的print方法其实就是我们设计模式原理图中的operation方法)

        @Test
        public void testComponent() {
    
            // 叶节点
            MenuItem slr = new MenuItem("烧鹿茸", "好吃美味,价格实惠", Boolean.FALSE, 180.0);
            MenuItem sxz = new MenuItem("烧熊掌", "好吃美味,价格实惠", Boolean.FALSE, 190.0);
    
            MenuItem hsr = new MenuItem("红烧肉", "好吃美味,价格实惠", Boolean.FALSE, 36.0);
            MenuItem hsqz = new MenuItem("红烧茄子", "好吃美味,价格实惠", Boolean.TRUE, 14.0);
            MenuItem hsjk = new MenuItem("红烧鸡块", "好吃美味,价格实惠", Boolean.FALSE, 38.0);
            MenuItem yxrs = new MenuItem("鱼香肉丝", "好吃美味,价格实惠", Boolean.FALSE, 22.0);
            MenuItem ssbc = new MenuItem("手撕包菜", "好吃美味,价格实惠", Boolean.TRUE, 12.0);
    
            // 组合节点
            Menu menu = new Menu("家常菜", "美味家常菜");
            menu.add(hsr);
            menu.add(hsqz);
            menu.add(hsjk);
            menu.add(yxrs);
            menu.add(ssbc);
            // 子节点print方法
            slr.print();
            sxz.print();
            // 组合节点print方法
            menu.print();
    
        }
    
    运行结果

    总结

    想必通过上面的示例,各位小伙伴已经对组合模式有了一定的认知,对这种设计模式适用的场景也有了比较明确认识:如果在一个业务中,单个对象和对象的集合需要具备同样的类型和方法,那组合模式就是最佳选择,比如我们这里的菜单和菜单组合,当然,更具体的应用场景,还需要各位小伙伴结合具体应用场景分析,但是学习的时候多思考应用场景才是学习正在的目的。好了,今天就到这里吧!

  • 相关阅读:
    jackson 枚举 enum json 解析类型 返回数字 或者自定义文字 How To Serialize Enums as JSON Objects with Jackson
    Antd Pro V5 中ProTable 自定义查询参数和返回值
    ES6/Antd 代码阅读记录
    es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?
    Antd Hooks
    使用.Net Core开发WPF App系列教程(其它 、保存控件内容为图片)
    使用.Net Core开发WPF App系列教程( 三、与.Net Framework的区别)
    使用.Net Core开发WPF App系列教程( 四、WPF中的XAML)
    使用.Net Core开发WPF App系列教程( 二、在Visual Studio 2019中创建.Net Core WPF工程)
    使用.Net Core开发WPF App系列教程( 一、.Net Core和WPF介绍)
  • 原文地址:https://www.cnblogs.com/caoleiCoding/p/15449801.html
Copyright © 2011-2022 走看看