JavaScript设计模式「基于ES2024」:结构型模式-组合模式

53 阅读1分钟

组合模式允许开发者将对象组合成树形结构来表现"部分-整体"的层次结构。这种模式使得客户端可以统一地对待单个对象和组合对象。

// 组件接口
class FileSystemComponent {
    constructor(name) {
        this.name = name;
    }

    display(indent = '') {
        throw new Error('Method not implemented');
    }

    getSize() {
        throw new Error('Method not implemented');
    }

    isDirectory() {
        return false;
    }
}

// 叶子节点:文件
class File extends FileSystemComponent {
    #size;

    constructor(name, size) {
        super(name);
        this.#size = size;
    }

    display(indent = '') {
        console.log(`${indent}File: ${this.name} (${this.#size} bytes)`);
    }

    getSize() {
        return this.#size;
    }
}

// 复合节点:目录
class Directory extends FileSystemComponent {
    #children;

    constructor(name) {
        super(name);
        this.#children = [];
    }

    add(component) {
        this.#children.push(component);
    }

    remove(component) {
        const index = this.#children.indexOf(component);
        if (index !== -1) {
            this.#children.splice(index, 1);
        }
    }

    display(indent = '') {
        console.log(`${indent}Directory: ${this.name}`);
        this.#children.forEach(child => child.display(indent + '  '));
    }

    getSize() {
        return this.#children.reduce((sum, child) => sum + child.getSize(), 0);
    }

    isDirectory() {
        return true;
    }

    findChild(name) {
        return this.#children.find(child => child.name === name);
    }
}

// 文件系统类
class FileSystem {
    #root;

    constructor() {
        this.#root = new Directory('Root');
    }

    addComponent(component, path = []) {
        let currentDir = this.#root;
        for (const dirName of path) {
            let dir = currentDir.findChild(dirName);
            if (!dir || !dir.isDirectory()) {
                dir = new Directory(dirName);
                currentDir.add(dir);
            }
            currentDir = dir;
        }
        currentDir.add(component);
    }

    display() {
        this.#root.display();
    }

    getTotalSize() {
        return this.#root.getSize();
    }
}

// 使用示例
const fileSystem = new FileSystem();

fileSystem.addComponent(new File('document.txt', 100));
fileSystem.addComponent(new File('image.jpg', 2000));
fileSystem.addComponent(new Directory('Empty Directory'));
fileSystem.addComponent(new File('config.json', 50), ['config']);
fileSystem.addComponent(new File('logo.png', 1000), ['images']);
fileSystem.addComponent(new File('script.js', 200), ['src', 'js']);
fileSystem.addComponent(new File('style.css', 150), ['src', 'css']);

console.log("File System Structure:");
fileSystem.display();

console.log(`\nTotal Size: ${fileSystem.getTotalSize()} bytes`);

实现思路

  1. FileSystemComponent:这是组件的抽象基类,定义了文件系统中所有元素的通用接口。 isDirectory() 方法,默认返回 false

  2. File:这是叶子节点,代表文件系统中的文件。它有具体的大小,并且不能包含其他组件。

  3. Directory:这是复合节点,代表文件系统中的目录。它可以包含其他文件或目录。

    • 使用私有字段 #children 来存储子组件。
    • 实现了 addremove 方法来管理子组件。
    • 重写 isDirectory() 方法,返回 true
    • 添加了 findChild(name) 方法,用于在目录中查找子组件。
  4. FileSystem:这是客户端使用的主要类,封装了整个文件系统的结构。

    • 使用私有字段 #root 来存储根目录。
    • 提供了 addComponent 方法,允许在任意路径添加新的组件。
    • 实现了 displaygetTotalSize 方法来展示结构和计算总大小。

优点

  • 统一接口:客户端可以统一地处理简单和复杂的元素。
  • 简化客户端代码:客户端不需要知道是在处理叶子节点还是复合节点。
  • 易于添加新类型的组件:可以轻松扩展新的文件类型或特殊目录。
  • 创建复杂的树结构:可以用来表示任意复杂的层次结构。