设计模式详解——组合模式

共 5432字,需浏览 11分钟

 ·

2021-10-20 14:49

前言

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

组合模式

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

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

使用场景

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

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

要点

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

示例

下面我们通过一个具体实例来演示下组合模式到底是如何工作的。这里我们直接用了《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 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 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();

    }
运行结果

总结

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

- END -


浏览 23
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报