23种设计模式之组合(Composite)模式

959 阅读4分钟

1、定义

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

2、模式结构

组合模式由三部分组成:

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

3、实例

3.1 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 void print() {
        throw new UnsupportedOperationException("不支持打印操作");
    }
    
    public String getContent() {
        throw new UnsupportedOperationException("不支持获取内容操作");
    }
}

3.2 文件夹类Folder(容器构件)

public class Folder extends Component {
    
    private String name;
    
    private List<Component> componentList = new ArrayList<>();
    
    private int level;
    
    public Folder(String name) {
        this.name = name;
    }
    
    @Override
    public String getName() {
        return name;
    }
    
    @Override
    public void add(Component component) {
        componentList.add(component);
    }
    
    @Override
    public void remove(Component component) {
        componentList.remove(component);
    }
    
    @Override
    public void print() {
        System.out.println(getName());
        if (level == 0) {
            level = 1;
        }
        String prefix = "";
        for (int i = 0; i < level; i++) {
            prefix += "\t- ";
        }
        for (Component component : componentList) {
            if (component instanceof Folder) {
                ((Folder)component).level = level + 1;
            }
            System.out.print(prefix);
            component.print();
        }
        level = 0;
    }
}

3.3 文件类File(叶子构件)

public class File extends Component {
    
    private String name;
    
    private String content;
    
    public File(String name, String content) {
        this.name = name;
        this.content = content;
    }
    
    @Override
    public String getName() { 
        return name;
    }
    
    @Override
    public void print() {
        System.out.println(getName());
    }
    
    @Override
    public String getContent() {
        return content;
    }
}

3.4 客户端调用

public class Client {
    
    public static void main(String[] args) {
        Folder DSFolder = new Folder("设计模式资料");
        File note1 = new File("组合模式.md", "组合模式组合多个对象形成树形结构以表示具有\"整体-部分\"关系的层次结构");
        File note2 = new File("工厂方法模式.md", "工厂方法模式定义一个用于创建对象的接口,让子类决定将哪一个类实例化。");
        
        DSFolder.add(note1);
        DSFolder.add(note2);
        
        Folder codeFolder = new Folder("样例代码");
        File readme = new File("README.md", "# 设计模式示例代码项目");
        Folder srcFolder = new Folder("src");
        File code = new File("组合模式示例.java", "这是组合模式的示例代码");
        
        srcFolder.add(code);
        codeFolder.add(readme);
        codeFolder.add(srcFolder);
        DSFolder.add(codeFolder);
        
        DSFolder.print();
    }
}

4、适用场景

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

5、在JDK集合的应用

6、优缺点

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

7、安全方式VS透明方式

  • 安全方式在抽象组件中只定义一些默认的行为或属性,它是把容器节点和叶子节点彻底分开;透明方式是把用来组合使用的方法放到抽象类中,不管叶子对象和容器对象都有相同的结构,通过判断确定是叶子节点还是容器节点,如果处理不当,这个会在运行期出现问题,不是很建议的方式。
  • 安全方式与依赖倒置原则冲突;透明方式的好处就是它基本遵循了依赖倒置原则,方便系统进行扩展。
  • 安全方式在遍历树形结构的时候需要进行强制类型转换;在透明方式下,遍历整个树形结构是比较容易的,不用进行强制类型转换。

源代码:github.com/freedom9/de…