把书读薄 | 《设计模式之美》设计模式与范式(结构型-组合模式)

简介: 本文对应设计模式与范式:结构型(53),组合模式 (Composite Pattern),又称 部分整体模式,不要跟前面讲的类间的 组合关系 混淆!!!组合模式是用来 处理树形结构数据(对象集合) 的。数据必须要能表示成树形结构,导致日常开发中不怎么常用,但如果数据能满足树形结构,应用此模式有奇效(代码简洁)。

0x1、定义


将对象组合成 树形结构 以表示整个部分的层次结构,让用户可以 统一对待 对象和对象的组合。


云里雾里?写个简单例子帮助理解~


0x2、写个简单例子


还是奶茶店的例子(树形结构):


网络异常,图片无法展示
|


不难分解成两类,并假设有后面的需求:


  • 菜单:菜单名、描述信息、增删子菜单或饮品;


  • 饮品:名称、描述信息、价格、打印信息


下面我们用常规代码实现一波:


// 抽象出一个饮品的类(此处是为了偷懒减少方法重写)
public abstract class Drink {
    protected String name;
    protected String desc;
    protected int price;
    public Drink(String name, String desc, int price) {
        this.name = name;
        this.desc = desc;
        this.price = price;
    }
    protected String printMsg() {
        return "*【" + name + "】- " + desc + " - " + price + "元";
    }
}
// 对应各种子类饮品
public class FruitTea extends Drink {
    public FruitTea(String name, String desc, int price) { super(name, desc, price); }
}
public class IceCream extends Drink {
    public IceCream(String name, String desc, int price) { super(name, desc, price); }
}
public class MilkTea extends Drink {
    public MilkTea(String name, String desc, int price) { super(name, desc, price); }
}
// 菜单类(支持子菜单、和饮品增删,此处偷懒只实现添加方法~)
public class Menu {
    private String name;
    private String desc;
    private List<Menu> subMenus = new ArrayList<>();
    private List<FruitTea> fruitTeas = new ArrayList<>();
    private List<IceCream> iceCreams = new ArrayList<>();
    private List<MilkTea> milkTeas = new ArrayList<>();
    public Menu(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }
    void addMenu(Menu menu) { subMenus.add(menu); }
    void addFruitTea(FruitTea tea) { fruitTeas.add(tea); }
    void addIceCream(IceCream iceCream) { iceCreams.add(iceCream); }
    void addMilkTea(MilkTea tea) { milkTeas.add(tea); }
    String printMsg() {
        StringBuilder sb = new StringBuilder("【菜单】 → " + name + ":" + desc);
        for(Menu menu: subMenus) {
            sb.append("\n【子菜单】 → ").append(menu.name).append(":").append(menu.desc).append("\n");
            for(FruitTea tea: menu.fruitTeas) { sb.append(tea.printMsg()).append("\n"); }
            for(IceCream iceCream: menu.iceCreams) { sb.append(iceCream.printMsg()).append("\n"); }
            for(MilkTea milkTea: menu.milkTeas) { sb.append(milkTea.printMsg()).append("\n"); }
        }
        return sb.toString();
    }
}
// 测试用例(就是把饮品加到菜单里,然后加入大菜单里~)
public class MenuTest {
    public static void main(String[] args) {
        Menu mainMenu = new Menu("大菜单", "包含子菜单和所有饮品");
        Menu fruitTeaMenu = new Menu("果茶", "真鲜果茶");
        Menu iceCreamMenu = new Menu("冰淇淋", "鲜冰淇淋");
        Menu milkTeaMenu = new Menu("奶茶", "现煮奶茶");
        FruitTea lemonWater = new FruitTea("冰鲜柠檬水", "本店好评第1名", 5);
        FruitTea hitOrange = new FruitTea("棒打鲜橙", "深圳橙子人气飙升第5名", 7);
        IceCream milkShake = new IceCream("草莓摇摇奶昔", "本店人气飙升第4名", 7);
        IceCream sundae = new IceCream("草莓雪王大圣代", "龙岗区草莓圣代人气飙升第4名", 7);
        MilkTea jelly = new MilkTea("外婆烧仙草(大杯)", "现熬嫩滑烧仙草", 10);
        MilkTea milkTea = new MilkTea("霸霸椰果奶茶", "Q弹椰果、融入香醇奶茶", 9);
        fruitTeaMenu.addFruitTea(lemonWater);
        fruitTeaMenu.addFruitTea(hitOrange);
        iceCreamMenu.addIceCream(milkShake);
        iceCreamMenu.addIceCream(sundae);
        milkTeaMenu.addMilkTea(jelly);
        milkTeaMenu.addMilkTea(milkTea);
        mainMenu.addMenu(fruitTeaMenu);
        mainMenu.addMenu(iceCreamMenu);
        mainMenu.addMenu(milkTeaMenu);
        System.out.println(mainMenu.printMsg());
    }
}


运行输出结果如下


网络异常,图片无法展示
|


上述这种实现方式,增删饮品或子菜单,原有代码要进行好几处修改,扩展性较差。对于这种 可以表示成树结构,业务需求通过递归遍历来实现的场景,可以直接套组合模式进行改造:


// 抽象组件(抽象出可以代表菜单、又可以代表饮品的类)
public abstract class AbstractComponent {
    abstract void add(AbstractComponent component);
    abstract AbstractComponent get(int index);
    abstract String printMsg();
}
// 叶子组件(这里是为了偷懒,不用重写那么那么多方法~)
public class AbstractDrink extends AbstractComponent {
    protected String name;
    protected String desc;
    protected int price;
    public AbstractDrink(String name, String desc, int price) {
        this.name = name;
        this.desc = desc;
        this.price = price;
    }
    @Override void add(AbstractComponent menu) { /* 饮品不需要使用这个 */ }
    @Override AbstractComponent get(int index) { return null; }
    @Override String printMsg() {
        return "*【" + name + "】- " + desc + " - " + price + "元";
    }
}
// 具体叶子结点
public class FruitTea extends AbstractDrink {
    public FruitTea(String name, String desc, int price) { super(name, desc, price); }
}
public class IceCream extends AbstractDrink {
    public IceCream(String name, String desc, int price) { super(name, desc, price); }
}
public class MilkTea extends AbstractDrink {
    public MilkTea(String name, String desc, int price) { super(name, desc, price); }
}
// 容器组件
public class AbstractMenu extends AbstractComponent {
    private String name;
    private String desc;
    private List<AbstractComponent> menus;
    public AbstractMenu(String name, String desc) {
        this.name = name;
        this.desc = desc;
        this.menus = new ArrayList<>();
    }
    @Override void add(AbstractComponent component) { menus.add(component); }
    @Override AbstractComponent get(int index) { return menus.get(index); }
    @Override String printMsg() {
        StringBuilder sb = new StringBuilder("【菜单】 → " + name + ":" + desc + "\n");
        for(AbstractComponent menu: menus) {
            sb.append(menu.printMsg()).append("\n");
        }
        return sb.toString();
    }
}
// 测试用例
public class MenuTest {
    public static void main(String[] args) {
        AbstractComponent mainMenu = new AbstractMenu("大菜单", "包含子菜单和所有饮品");
        AbstractComponent fruitTeaMenu = new AbstractMenu("果茶", "真鲜果茶");
        AbstractComponent iceCreamMenu = new AbstractMenu("冰淇淋", "鲜冰淇淋");
        AbstractComponent milkTeaMenu = new AbstractMenu("奶茶", "现煮奶茶");
        AbstractComponent lemonWater = new FruitTea("冰鲜柠檬水", "本店好评第1名", 5);
        AbstractComponent hitOrange = new FruitTea("棒打鲜橙", "深圳橙子人气飙升第5名", 7);
        AbstractComponent milkShake = new IceCream("草莓摇摇奶昔", "本店人气飙升第4名", 7);
        AbstractComponent sundae = new IceCream("草莓雪王大圣代", "龙岗区草莓圣代人气飙升第4名", 7);
        AbstractComponent jelly = new MilkTea("外婆烧仙草(大杯)", "现熬嫩滑烧仙草", 10);
        AbstractComponent milkTea = new MilkTea("霸霸椰果奶茶", "Q弹椰果、融入香醇奶茶", 9);
        // 直接add,不需要具体区分,强调了统一处理~
        fruitTeaMenu.add(lemonWater);
        fruitTeaMenu.add(hitOrange);
        iceCreamMenu.add(milkShake);
        iceCreamMenu.add(sundae);
        milkTeaMenu.add(jelly);
        milkTeaMenu.add(milkTea);
        mainMenu.add(fruitTeaMenu);
        mainMenu.add(iceCreamMenu);
        mainMenu.add(milkTeaMenu);
        System.out.println(mainMenu.printMsg());
    }
}


运行输出结果如下


网络异常,图片无法展示
|


代码看着多,其实很简单,完美呼应组合模式的两个关键:树形结构 + 统一对待。顺带画出UML类图和角色介绍:


网络异常,图片无法展示
|


  • Component (抽象组件) → 为组合中的对象声明接口,客户端通过这个接口访问和管理整个对象结构;


  • Composite (容器组件) → 继承抽象组件,包含多个结点的复合对象,它下面还可以有其他容器组件或叶子组件;


  • Leaf (叶子组件) → 继承抽象组件,定义实现叶子对象的行为,原子对象,它下面不会有其他组件了。


另外,还根据 抽象组件是否声明了用于管理成员的方法,划分为:透明组合模式安全组合模式,上面的例子就是透明组合模式(标准形式),好处是可以确保所有组件类都有相同的接口,缺点是 不够安全,叶子组件和容器组件在本质上是有区别的,编译器叶子结点调容器组件方法不会报错,但运行阶段调用可能会出错(没提供响应的错误处理代码的话~)


优点:


客户端无需操心面对的是组合对象还是叶结点对象,不需要写一大堆if语句来保证对正确的对象调用了正确的方法,通常只需要对整个结构调用一个方法并执行操作即可。


缺点:


新增组件会带来一些问题,如很难限制组合中的组件类型,在检测组件类型时不能依靠编译器类型来束完成,必须在运行时动态检测。


组合模式的一些经典应用例子:


  • java.awt
  • Java集合
  • Mybatis SqlNode


限于篇幅就不展开了,例子详细讲解可见:设计模式 | 组合模式及典型应用


相关文章
|
4月前
|
设计模式 JavaScript 前端开发
js设计模式【详解】—— 组合模式
js设计模式【详解】—— 组合模式
52 7
|
2月前
|
设计模式 Java
Java设计模式:组合模式的介绍及代码演示
组合模式是一种结构型设计模式,用于将多个对象组织成树形结构,并统一处理所有对象。例如,统计公司总人数时,可先统计各部门人数再求和。该模式包括一个通用接口、表示节点的类及其实现类。通过树形结构和节点的通用方法,组合模式使程序更易扩展和维护。
Java设计模式:组合模式的介绍及代码演示
|
2月前
|
设计模式 存储 安全
Java设计模式-组合模式(13)
Java设计模式-组合模式(13)
|
5月前
|
设计模式 存储 安全
Java设计模式:组合模式之透明与安全的两种实现(七)
Java设计模式:组合模式之透明与安全的两种实现(七)
|
5月前
|
设计模式 Java
Java设计模式之组合模式详解
Java设计模式之组合模式详解
|
5月前
|
设计模式
组合模式-大话设计模式
组合模式-大话设计模式
|
5月前
|
设计模式 程序员
结构型设计模式之适配器模式
结构型设计模式之适配器模式
|
15天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
2月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
17天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###