27 设计模式:组合模式--结构型模式

132 阅读5分钟

结构型模式之:

  • 适配器模式
  • 桥接模式
  • 代理模式
  • 装饰模式
  • 组合模式
  • 外观模式
  • 享元模式

1.什么是组合模式

组合模式是一种结构型设计模式,它允许将对象组合成树形结构以表示“部分-整体”的层次关系。组合模式使得客户端可以统一处理单个对象和对象集合,而无需区分它们之间的差异。

image.png

这个组合模式,更像是什么呢?更像是递归树,忽略每个分支的下一个细节,通过泛型的方式去递归处理下一个分支。

举一个组合模式的例子:假设我们要创建一个文件系统的抽象表示,其中包含文件和文件夹两种类型的节点。文件夹可以包含文件和其他文件夹,文件是叶子节点,不包含其他节点。

首先,我们定义一个抽象基类 FileSystemComponent,它包含了文件和文件夹的公共操作:

// 抽象基类
public abstract class FileSystemComponent {
    protected String name;

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

    public abstract void print();
}

然后,我们定义文件类 File 和文件夹类 Folder,它们都是 FileSystemComponent 的子类:

// 文件类
public class File extends FileSystemComponent {
    public File(String name) {
        super(name);
    }

    @Override
    public void print() {
        System.out.println("File: " + name);
    }
}

// 文件夹类
import java.util.ArrayList;
import java.util.List;

public class Folder extends FileSystemComponent {
    private List<FileSystemComponent> children;

    public Folder(String name) {
        super(name);
        children = new ArrayList<>();
    }

    public void add(FileSystemComponent component) {
        children.add(component);
    }

    public void remove(FileSystemComponent component) {
        children.remove(component);
    }
    
    //组合部分
    @Override
    public void print() {
        System.out.println("Folder: " + name);
        for (FileSystemComponent component : children) {
            component.print();
        }
    }
}

在这个例子中,Folder 类可以包含其他的 FileFolder 对象,形成了一个树形结构。print() 方法通过递归调用,可以打印出整个文件系统的层次结构。

使用组合模式,我们可以轻松地操作文件和文件夹,统一了对它们的处理方式,使得客户端代码更加简洁和灵活。

2.组合模式的使用

前一段时间项目需求,针对服务端传送过来的json数据进行处理,但是json是负责的,类似于树结构一样,没有办法判定是不是到了最小拆分节点。也同样是使用到了组合模式。

举一个简单的例子,假设我们有一个 JSON 数据表示一个用户信息,其中包含了用户的基本信息和订单信息。该 JSON 数据可能如下所示:

{
  "name": "John",
  "age": 30,
  "email": "john@example.com",
  "orders": [
    {
      "id": "123456",
      "product": "Laptop",
      "price": 999.99
    },
    {
      "id": "789012",
      "product": "Smartphone",
      "price": 699.99
    }
  ]
}

怎么解析处理一个复杂的json呢?,一个复杂的json可能包含,jsonObject,jsonArray,JSONPrimitive(最小节点)

// 抽象基类 abstract class JSONComponent { public abstract void print(); }
// JSON 对象类
class JSONObject extends JSONComponent {
    private List<JSONComponent> components = new ArrayList<>();

    public void addComponent(JSONComponent component) {
        components.add(component);
    }

    @Override
    public void print() {
        System.out.println("{");
        for (JSONComponent component : components) {
            component.print();
        }
        System.out.println("}");
    }
}
// JSON 数组类
class JSONArray extends JSONComponent {
    private List<JSONComponent> elements = new ArrayList<>();

    public void addElement(JSONComponent element) {
        elements.add(element);
    }

    @Override
    public void print() {
        System.out.println("[");
        for (JSONComponent element : elements) {
            element.print();
        }
        System.out.println("]");
    }
}
// JSON 基本数据类型类
class JSONPrimitive extends JSONComponent {
    private String value;

    public JSONPrimitive(String value) {
        this.value = value;
    }

    @Override
    public void print() {
        System.out.println("\"" + value + "\"");
    }
}
public class Main {
    public static void main(String[] args) {
        // 构建 JSON 数据
        JSONObject json = new JSONObject();
        json.addComponent(new JSONPrimitive("name: John"));
        json.addComponent(new JSONPrimitive("age: 30"));
        json.addComponent(new JSONPrimitive("email: john@example.com"));

        JSONArray orders = new JSONArray();
        orders.addElement(new JSONObject().addComponent(new JSONPrimitive("id: 123456")).addComponent(new JSONPrimitive("product: Laptop")).addComponent(new JSONPrimitive("price: 999.99")));
        orders.addElement(new JSONObject().addComponent(new JSONPrimitive("id: 789012")).addComponent(new JSONPrimitive("product: Smartphone")).addComponent(new JSONPrimitive("price: 699.99")));
        json.addComponent(orders);

        // 打印 JSON 数据
        json.print();
    }
}

在这个示例中,我们定义了抽象基类 JSONComponent,以及具体的子类 JSONObjectJSONArrayJSONPrimitive,分别用于表示 JSON 对象、JSON 数组和基本数据类型。然后我们构建了一个包含用户信息和订单信息的 JSON 数据,并通过组合模式来表示和打印这个 JSON 数据。

3.组合模式的使用场景

  1. 树形结构的数据表示: 当数据具有层次结构,且每个节点都可以包含子节点时,可以使用组合模式。例如,文件系统中的文件和文件夹,以及菜单系统中的菜单和菜单项等都可以使用组合模式来表示。
  2. 统一处理叶子和组合对象: 组合模式允许客户端统一处理叶子对象和组合对象,因为它们都实现了相同的接口。这样客户端可以一致性地处理整个对象结构。
  3. 简化客户端代码: 客户端无需关心对象是叶子还是组合,只需通过统一的接口来操作对象。这简化了客户端的代码,使其更加清晰和易于维护。
  4. 动态组合对象: 组合模式允许动态地向组合对象中添加或移除子对象,而无需修改客户端代码。这使得对象的组织结构可以灵活地调整和扩展。

4.组合模式的优缺点

优点:

  • 你可以利用多态和递归机制更方便地使用复杂树结构。
  • 开闭原则。 无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象树的一部分。

缺点:

  • 设计复杂性增加: 实现组合模式需要定义抽象组件、叶子组件和组合组件等多个类,并且需要合理地设计它们之间的关系,因此会增加系统的设计复杂性。
  • 不易限制组件类型: 组合模式将叶子对象和组合对象统一对待,因此可能导致在组合对象中添加了不合适的子对象,或者在叶子对象中执行了不适当的操作。
  • 不适合过于简单或过于复杂的情况: 对于过于简单的对象结构或过于复杂的对象结构,组合模式可能会显得不够灵活或不够直观,不适合使用。
  • 性能问题: 组合模式可能会在遍历整个对象结构时产生额外的开销,特别是在深层次的嵌套结构中,遍历和操作可能会导致性能下降