《一天一模式》— 组合模式

旧街凉风 提交于 2020-04-09 00:55:10

一、组合模式的概念

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

听懂了这句话就不用往下看了,说明你会了。

听不懂我觉得也正常,如果用一句话能学会就没人看书了。像我这种笨人,都是学会了一个模式,然后往它的定义上套。

二、什么时候使用组合模式

  • 需要某种树形结构,可以容纳菜单、子菜单和菜单项;
  • 需要能够在各个菜单之间随意游走,同级的菜单类型可以不同;
  • 需要能够在遍历时,随意遍历各个菜单;

也就是说,树形结构中的菜单,子菜单是容器,菜单项是内容。

组合模式是要让容器和内容具有一致性,创造出递归结构,并且在某一个场景下,容器和内容可以无差别使用,图中第二层既可以是Leaf,也可以是Node。

常见的场景有:部门树、功能菜单树、字典树等等,在遇到可递归的树形结构需求时,都可以考虑一下是否需要使用组合模式。

三、怎么使用组合模式

以一个需求为例进行说明,还是汽车相关的。

汽车厂商将汽车分为品牌、车系、车型、他们之前是属性关系,品牌下有多个车系,车系下有多个车型,具体例子:

品牌是奔驰。

奔驰下有多个车系,A级,C级,E级。

每个车系下有多个车型,A级下有A180,A200,A220。

需求是,打印出所有的车系和车型,在打印时进行名称美化,传入车系和车型都可以完成名称美化。

3.1 不使用组合模式带来的弊端

不使用组合模式来创建这个结构。

  • 首先创建一个品牌对象(Brand),里面有一个车系(Series)的ArrayList集合;
  • 然后创建一个车系对象(Series),里面有一个车型(Model)的ArrayList集合;
  • 最后创建一个车系对象(Model);

一个典型的一对多关系,具体代码如下:

/**
 * 品牌
 */
public class Brand {

    private List<Series> series = new ArrayList<Series>();

    private String name;

    public Brand(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void add(Series s) {
        series.add(s);
    }

    public void remove(Series s) {
        series.remove(s);
    }

    public Series getChild(int i) {
        return series.get(i);
    }

    public void print() {
        System.out.println(getName());
        Iterator<Series> iterator = series.iterator();
        while(iterator.hasNext()) {
            iterator.next().print();
        }
    }

}

/**
 * 车系
 */
public class Series {

    private List<Model> model = new ArrayList<Model>();

    private String name;

    public Series(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void add(Model m) {
        model.add(m);
    }

    public void remove(Model m) {
        model.remove(m);
    }

    public Model getChild(int i) {
        return model.get(i);
    }

    public void print() {
        System.out.println(getName());
        Iterator<Model> iterator = model.iterator();
        while (iterator.hasNext()) {
            iterator.next().print();
        }
    }

}

/**
 * 车型
 */
public class Model {

    private String name;

    public Model(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void print() {
        System.out.println(getName());
    }

}

/**
 * 运行输出
 * 奔驰
 * 奔驰A系
 * A100
 * A200
 * 奔驰C系
 * C100
 * C200
 */
public class Client {

    public static void main(String[] args) {
        Brand brand = new Brand("奔驰");
        Series series = new Series("奔驰A系");
        brand.add(series);
        Model a100 = new Model("A100");
        Model a200 = new Model("A200");
        series.add(a100);
        series.add(a200);
        Series series1 = new Series("奔驰C系");
        brand.add(series1);
        Model c100 = new Model("C100");
        Model c200 = new Model("C200");
        series1.add(c100);
        series1.add(c200);
        brand.print();
    }

}

需求是,打印出所有的车系和车型,在打印时进行名称美化,传入车系和车型都可以完成名称美化。

第一个弊端 — 通用性差

这里要写这个函数就有问题,由于车系和车型的类型不同,所以要单独编写函数,实现基本一致:

public String beautiful(Series series) {
    return "★" + series.getName() + "★";
}

public String beautiful(Model model) {
    return "★" + model.getName() + "★";
}

第二个弊端 — 结构不灵活

现在需求有变更:

车系是一个树形结构,也就是说Series对象的子对象中,可以存放车型,也可以存放车系。

回顾一下Series的代码:

public class Series {

    private List<Model> model = new ArrayList<Model>();

    ......

}

用于存放子对象的集合,泛型类型是Model,不能保存Series对象。

3.2 使用组合模式带来解决这个问题

直接上类图,使用组合模式后的类图如下:

回顾概念:

  • 将对象组合成树形结构以表示“部分-整体”的层次结构
  • 组合模式使得用户对单个对象和组合对象的使用具有一致性

现在,品牌、车系、车型都继承自组件(Component)。品牌中的集合,泛型类型是Component,车系中的集合泛型类型也是Component。这样就做到了概念中的两点,对象还是树形结构来表示部分和整体。同时,由于对象本身继承自Component,它内部的子对象集合也是Component,所以做到了一致性。

下面是组合模式下的代码:

/**
 * 组件对象
 */
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 Component getChild(int i) {
        throw new UnsupportedOperationException();
    }

    public void print() {
        throw new UnsupportedOperationException();
    }

    public String beautiful(Component component) {
        return "★" + component.getName() + "★";
    }

}

/**
 * 品牌,继承自Component
 */
public class Brand extends Component {

    // 内部不在是存放具体类型,现在改成了Component类型,继承自Component的对象都可以加入
    private List<Component> series = new ArrayList<Component>();

    private String name;

    public Brand(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void add(Component component) {
        series.add(component);
    }

    public void remove(Component component) {
        series.remove(component);
    }

    public Component getChild(int i) {
        return series.get(i);
    }

    public void print() {
        System.out.println(getName());
        Iterator<Component> iterator = series.iterator();
        while(iterator.hasNext()) {
            iterator.next().print();
        }
    }

}

/**
 * 车系,继承自Component
 */
public class Series extends Component {

    // 内部不在是存放具体类型,现在改成了Component类型,继承自Component的对象都可以加入
    private List<Component> model = new ArrayList<Component>();

    private String name;

    public Series(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void add(Component component) {
        model.add(component);
    }

    public void remove(Component component) {
        model.remove(component);
    }

    public Component getChild(int i) {
        return model.get(i);
    }

    public void print() {
        System.out.println(beautiful(this));
        Iterator<Component> iterator = model.iterator();
        while (iterator.hasNext()) {
            iterator.next().print();
        }
    }

}

/**
 * 车型
 */
public class Model extends Component {

    private String name;

    public Model(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void print() {
        System.out.println(beautiful(this));
    }

}

组合模式的使用方式:

public class Client {

    public static void main(String[] args) {
        // 创建品牌
        Component brand = new Brand("奔驰");
        // 创建车系A
        Component series = new Series("奔驰A系");
        // 把车系A加到品牌
        brand.add(series);
        // 创建两个车型
        Component a100 = new Model("A100");
        Component a200 = new Model("A200");
        series.add(a100);
        series.add(a200);

        // 创建车系C
        Component series1 = new Series("奔驰C系");
        // 加到品牌
        brand.add(series1);

        // 在创建2个车型添加到车系C
        Component c100 = new Model("C100");
        Component c200 = new Model("C200");
        series1.add(c100);
        series1.add(c200);
        // 这里!在车型c100和c200平级的节点,创建一个子车系
        Component series2 = new Series("奔驰C系运动款");
        series1.add(series2);
        // 在子车系下创建2个车型
        Component c100s = new Model("C100S");
        Component c200s = new Model("C200S");
        series2.add(c100s);
        series2.add(c200s);
        // 输出整个的树
        brand.print();
    }

}

// 输出结果
奔驰
★奔驰A系★
★A100★
★A200★
★奔驰C系★
★C100★
★C200★
★奔驰C系运动款★
★C100S★
★C200S★

用组合模式解决了需求中的2个点:

1.使用一个函数操作车系,车型

// 可以使用一个方式来操作车系、车型、品牌等对象,只要是这个树结构下的节点均可以操作
public String beautiful(Component component) {
    return "★" + component.getName() + "★";
}

2.随时可以添加子节点

// 在创建2个车型添加到车系C
Component c100 = new Model("C100");
Component c200 = new Model("C200");
series1.add(c100);
series1.add(c200);
// 这里!在车型c100和c200平级的节点,创建一个子车系
Component series2 = new Series("奔驰C系运动款");

输出结果是:

★奔驰C系★(车系)
  |-★C100★(车型)
  |-★C200★(车型)
  |-★奔驰C系运动款★(车系)
     |-★C100S★(车型)
     |-★C200S★(车型)

树的任意一级可以存放任意的对象(只要是继承自Component),这种方式解决了第二个需求。

四、总结

组合模式,我的一个简单理解是,在两个场景下应该去考虑使用:

  1. 当你的对象结构是树,树的各个层级并不是有一个对象组合而成的,但这些对象都有相同的业务操作(上面的例子中的display方法);
  2. 面相后续的需求,有可能每一层保存的对象类型也不一定相同(例如上面的例子中,车系下不只有车型,还有子车系);

组合模式的做法就很简单了,主要也是两点:

  1. 树上的所有节点都继承一个Component对象(抽象对象);
  2. 对象种的ArrayList的泛型对象使用Component;

以上就是组合模式的一些理解, 有不足之处请大家矫正,谢谢。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!