结构型模式

结构型设计模式关注类和对象的组合,通过继承或组合来构建更大的结构。本章将介绍JavaScript中常用的结构型设计模式。

适配器模式(Adapter)

适配器模式将一个类的接口转换成客户端期望的另一个接口,使原本不兼容的类可以一起工作。

基本实现

class OldAPI {
    getData(id) {
        return {
            id: id,
            fullName: '张三',
            yearsOld: 25,
            contactNumber: '13800138000'
        };
    }
}

class NewAPI {
    getUser(id) {
        return {
            id: id,
            name: { first: '三', last: '张' },
            age: 25,
            phone: '13800138000'
        };
    }
}

class APIAdapter {
    constructor(api) {
        this.api = api;
    }
    
    getData(id) {
        const oldData = this.api.getData(id);
        return {
            id: oldData.id,
            name: oldData.fullName,
            age: oldData.yearsOld,
            phone: oldData.contactNumber
        };
    }
}

class NewAPIAdapter {
    constructor(api) {
        this.api = api;
    }
    
    getData(id) {
        const newData = this.api.getUser(id);
        return {
            id: newData.id,
            name: `${newData.name.last}${newData.name.first}`,
            age: newData.age,
            phone: newData.phone
        };
    }
}

支付接口适配

class PaymentAdapter {
    static create(type) {
        const adapters = {
            alipay: AlipayAdapter,
            wechat: WechatPayAdapter,
            stripe: StripeAdapter
        };
        
        const Adapter = adapters[type];
        if (!Adapter) throw new Error(`不支持的支付方式: ${type}`);
        return new Adapter();
    }
}

class AlipayAdapter {
    async pay(amount, options) {
        const result = await this.alipaySDK.createOrder({
            totalAmount: amount,
            subject: options.description
        });
        return {
            success: result.code === '10000',
            transactionId: result.tradeNo,
            message: result.msg
        };
    }
    
    async refund(transactionId, amount) {
        const result = await this.alipaySDK.refund({
            tradeNo: transactionId,
            refundAmount: amount
        });
        return { success: result.code === '10000', refundId: result.refundNo };
    }
}

class WechatPayAdapter {
    async pay(amount, options) {
        const result = await this.wechatSDK.unifiedOrder({
            total_fee: amount * 100,
            body: options.description
        });
        return {
            success: result.return_code === 'SUCCESS',
            transactionId: result.prepay_id,
            message: result.return_msg
        };
    }
    
    async refund(transactionId, amount) {
        const result = await this.wechatSDK.refund({
            transaction_id: transactionId,
            refund_fee: amount * 100
        });
        return { success: result.return_code === 'SUCCESS', refundId: result.refund_id };
    }
}

装饰器模式(Decorator)

装饰器模式动态地给对象添加额外的职责,比生成子类更为灵活。

基本实现

class Coffee {
    cost() { return 10; }
    description() { return '咖啡'; }
}

class MilkDecorator {
    constructor(coffee) {
        this.coffee = coffee;
    }
    
    cost() { return this.coffee.cost() + 3; }
    description() { return `${this.coffee.description()} + 牛奶`; }
}

class SugarDecorator {
    constructor(coffee) {
        this.coffee = coffee;
    }
    
    cost() { return this.coffee.cost() + 1; }
    description() { return `${this.coffee.description()} + 糖`; }
}

class WhipDecorator {
    constructor(coffee) {
        this.coffee = coffee;
    }
    
    cost() { return this.coffee.cost() + 5; }
    description() { return `${this.coffee.description()} + 奶油`; }
}

let coffee = new Coffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
coffee = new WhipDecorator(coffee);

console.log(coffee.description());
console.log(`价格: ${coffee.cost()}元`);

函数装饰器

function logDecorator(fn) {
    return function(...args) {
        console.log(`调用函数,参数: ${JSON.stringify(args)}`);
        const result = fn.apply(this, args);
        console.log(`返回结果: ${JSON.stringify(result)}`);
        return result;
    };
}

function memoize(fn) {
    const cache = new Map();
    return function(...args) {
        const key = JSON.stringify(args);
        if (cache.has(key)) {
            console.log('从缓存返回');
            return cache.get(key);
        }
        const result = fn.apply(this, args);
        cache.set(key, result);
        return result;
    };
}

function debounce(fn, delay) {
    let timer = null;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
}

function throttle(fn, limit) {
    let inThrottle = false;
    return function(...args) {
        if (!inThrottle) {
            fn.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

function retry(fn, times = 3, delay = 1000) {
    return async function(...args) {
        for (let i = 0; i < times; i++) {
            try {
                return await fn.apply(this, args);
            } catch (error) {
                if (i === times - 1) throw error;
                console.log(`第${i + 1}次失败,${delay}ms后重试`);
                await new Promise(resolve => setTimeout(resolve, delay));
            }
        }
    };
}

类装饰器(ES7)

function readonly(target, key, descriptor) {
    descriptor.writable = false;
    return descriptor;
}

function log(target, key, descriptor) {
    const original = descriptor.value;
    descriptor.value = function(...args) {
        console.log(`调用 ${key},参数: ${JSON.stringify(args)}`);
        const result = original.apply(this, args);
        console.log(`返回: ${JSON.stringify(result)}`);
        return result;
    };
    return descriptor;
}

function debounce(delay) {
    return function(target, key, descriptor) {
        const original = descriptor.value;
        let timer = null;
        descriptor.value = function(...args) {
            clearTimeout(timer);
            timer = setTimeout(() => original.apply(this, args), delay);
        };
        return descriptor;
    };
}

class Person {
    @readonly
    name = '张三';
    
    @log
    greet(greeting) {
        return `${greeting}, 我是${this.name}`;
    }
    
    @debounce(300)
    search(query) {
        console.log(`搜索: ${query}`);
    }
}

代理模式(Proxy)

代理模式为其他对象提供代理以控制对这个对象的访问。

ES6 Proxy

const target = {
    name: '张三',
    age: 25,
    _secret: '密码'
};

const handler = {
    get(obj, prop) {
        if (prop.startsWith('_')) {
            throw new Error('私有属性不可访问');
        }
        console.log(`读取属性: ${prop}`);
        return obj[prop];
    },
    
    set(obj, prop, value) {
        if (prop.startsWith('_')) {
            throw new Error('私有属性不可修改');
        }
        console.log(`设置属性: ${prop} = ${value}`);
        obj[prop] = value;
        return true;
    },
    
    has(obj, prop) {
        return prop in obj && !prop.startsWith('_');
    },
    
    deleteProperty(obj, prop) {
        if (prop.startsWith('_')) {
            throw new Error('私有属性不可删除');
        }
        delete obj[prop];
        return true;
    }
};

const proxy = new Proxy(target, handler);

数据验证代理

function createValidatedObject(schema) {
    return new Proxy({}, {
        set(obj, prop, value) {
            if (!schema[prop]) {
                throw new Error(`未知属性: ${prop}`);
            }
            
            const { type, min, max, required } = schema[prop];
            
            if (required && value === undefined) {
                throw new Error(`${prop} 是必需的`);
            }
            
            if (type && typeof value !== type) {
                throw new Error(`${prop} 必须是 ${type} 类型`);
            }
            
            if (min !== undefined && value < min) {
                throw new Error(`${prop} 不能小于 ${min}`);
            }
            
            if (max !== undefined && value > max) {
                throw new Error(`${prop} 不能大于 ${max}`);
            }
            
            obj[prop] = value;
            return true;
        },
        
        get(obj, prop) {
            return obj[prop];
        }
    });
}

const userSchema = {
    name: { type: 'string', required: true },
    age: { type: 'number', min: 0, max: 150 },
    email: { type: 'string', required: true }
};

const user = createValidatedObject(userSchema);
user.name = '张三';
user.age = 25;
user.email = 'zhangsan@example.com';

缓存代理

function createCacheProxy(fn) {
    const cache = new Map();
    
    return new Proxy(fn, {
        apply(target, thisArg, args) {
            const key = JSON.stringify(args);
            
            if (cache.has(key)) {
                console.log('从缓存返回');
                return cache.get(key);
            }
            
            const result = target.apply(thisArg, args);
            cache.set(key, result);
            return result;
        }
    });
}

const expensiveCalculation = createCacheProxy(function(n) {
    console.log('执行计算...');
    return n * n * n;
});

虚拟代理(懒加载)

class ImageProxy {
    constructor(src) {
        this.src = src;
        this.image = null;
        this.loading = false;
        this.callbacks = [];
    }
    
    load() {
        if (this.image) return Promise.resolve(this.image);
        if (this.loading) return new Promise(resolve => this.callbacks.push(resolve));
        
        this.loading = true;
        
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => {
                this.image = img;
                this.loading = false;
                this.callbacks.forEach(cb => cb(img));
                this.callbacks = [];
                resolve(img);
            };
            img.onerror = reject;
            img.src = this.src;
        });
    }
    
    display(container) {
        container.innerHTML = '<div class="loading">加载中...</div>';
        this.load().then(img => {
            container.innerHTML = '';
            container.appendChild(img);
        });
    }
}

外观模式(Facade)

外观模式为子系统中的一组接口提供一个统一的入口。

基本实现

class CPU {
    freeze() { console.log('CPU冻结'); }
    jump(position) { console.log(`CPU跳转到 ${position}`); }
    execute() { console.log('CPU执行'); }
}

class Memory {
    load(position, data) { console.log(`内存加载: ${data}${position}`); }
}

class HardDrive {
    read(lba, size) { 
        console.log(`硬盘读取: LBA ${lba}, 大小 ${size}`);
        return '数据';
    }
}

class ComputerFacade {
    constructor() {
        this.cpu = new CPU();
        this.memory = new Memory();
        this.hardDrive = new HardDrive();
    }
    
    start() {
        this.cpu.freeze();
        this.memory.load(0, this.hardDrive.read(0, 1024));
        this.cpu.jump(0);
        this.cpu.execute();
        console.log('电脑启动完成');
    }
}

const computer = new ComputerFacade();
computer.start();

API外观

class APIFacade {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
        this.cache = new Map();
    }
    
    async getUser(id) {
        return this.fetchWithCache(`/users/${id}`);
    }
    
    async getUserPosts(userId) {
        return this.fetchWithCache(`/users/${userId}/posts`);
    }
    
    async getUserComments(userId) {
        return this.fetchWithCache(`/users/${userId}/comments`);
    }
    
    async getUserProfile(id) {
        const [user, posts, comments] = await Promise.all([
            this.getUser(id),
            this.getUserPosts(id),
            this.getUserComments(id)
        ]);
        
        return { user, posts, comments };
    }
    
    async fetchWithCache(path) {
        if (this.cache.has(path)) {
            return this.cache.get(path);
        }
        
        const response = await fetch(this.baseUrl + path);
        const data = await response.json();
        this.cache.set(path, data);
        return data;
    }
    
    clearCache() {
        this.cache.clear();
    }
}

组合模式(Composite)

组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。

基本实现

class Component {
    constructor(name) {
        this.name = name;
    }
    
    add(component) {
        throw new Error('不支持添加操作');
    }
    
    remove(component) {
        throw new Error('不支持移除操作');
    }
    
    display(depth = 0) {
        throw new Error('必须实现 display 方法');
    }
}

class Leaf extends Component {
    display(depth = 0) {
        console.log('  '.repeat(depth) + `- ${this.name}`);
    }
}

class Composite extends Component {
    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(depth = 0) {
        console.log('  '.repeat(depth) + `+ ${this.name}`);
        this.children.forEach(child => child.display(depth + 1));
    }
}

const root = new Composite('根目录');
const folder1 = new Composite('文件夹1');
const folder2 = new Composite('文件夹2');

folder1.add(new Leaf('文件1.txt'));
folder1.add(new Leaf('文件2.txt'));

folder2.add(new Leaf('文件3.txt'));

root.add(folder1);
root.add(folder2);
root.add(new Leaf('根文件.txt'));

root.display();

文件系统示例

class FileSystemItem {
    constructor(name) {
        this.name = name;
    }
    
    get size() { return 0; }
    ls() { return []; }
}

class File extends FileSystemItem {
    constructor(name, size) {
        super(name);
        this._size = size;
    }
    
    get size() { return this._size; }
    
    ls() { return [this.name]; }
}

class Directory extends FileSystemItem {
    constructor(name) {
        super(name);
        this.items = [];
    }
    
    add(item) {
        this.items.push(item);
        return this;
    }
    
    remove(item) {
        const index = this.items.indexOf(item);
        if (index > -1) this.items.splice(index, 1);
    }
    
    get size() {
        return this.items.reduce((sum, item) => sum + item.size, 0);
    }
    
    ls() {
        return this.items.map(item => item.name);
    }
    
    find(name) {
        if (this.name === name) return this;
        
        for (const item of this.items) {
            if (item instanceof Directory) {
                const found = item.find(name);
                if (found) return found;
            } else if (item.name === name) {
                return item;
            }
        }
        return null;
    }
}

const root = new Directory('root');
const docs = new Directory('docs');
const images = new Directory('images');

docs.add(new File('readme.md', 1024));
docs.add(new File('guide.pdf', 5120));

images.add(new File('logo.png', 2048));
images.add(new File('banner.jpg', 4096));

root.add(docs);
root.add(images);
root.add(new File('config.json', 256));

console.log(`总大小: ${root.size} bytes`);

桥接模式(Bridge)

桥接模式将抽象部分与实现部分分离,使它们都可以独立变化。

基本实现

class Renderer {
    renderCircle(radius) { throw new Error('必须实现'); }
    renderSquare(size) { throw new Error('必须实现'); }
}

class SVGRenderer extends Renderer {
    renderCircle(radius) {
        return `<circle r="${radius}" />`;
    }
    
    renderSquare(size) {
        return `<rect width="${size}" height="${size}" />`;
    }
}

class CanvasRenderer extends Renderer {
    renderCircle(radius) {
        return `ctx.arc(0, 0, ${radius}, 0, Math.PI * 2)`;
    }
    
    renderSquare(size) {
        return `ctx.rect(0, 0, ${size}, ${size})`;
    }
}

class Shape {
    constructor(renderer) {
        this.renderer = renderer;
    }
    
    draw() { throw new Error('必须实现'); }
}

class Circle extends Shape {
    constructor(renderer, radius) {
        super(renderer);
        this.radius = radius;
    }
    
    draw() {
        return this.renderer.renderCircle(this.radius);
    }
}

class Square extends Shape {
    constructor(renderer, size) {
        super(renderer);
        this.size = size;
    }
    
    draw() {
        return this.renderer.renderSquare(this.size);
    }
}

const svgCircle = new Circle(new SVGRenderer(), 50);
const canvasCircle = new Circle(new CanvasRenderer(), 50);

console.log(svgCircle.draw());
console.log(canvasCircle.draw());

享元模式(Flyweight)

享元模式通过共享对象来最小化内存使用。

基本实现

class TreeType {
    constructor(name, color, texture) {
        this.name = name;
        this.color = color;
        this.texture = texture;
    }
    
    draw(x, y) {
        console.log(`绘制${this.color}色的${this.name}在(${x}, ${y})`);
    }
}

class TreeFactory {
    static types = new Map();
    
    static getTreeType(name, color, texture) {
        const key = `${name}-${color}-${texture}`;
        
        if (!this.types.has(key)) {
            this.types.set(key, new TreeType(name, color, texture));
        }
        
        return this.types.get(key);
    }
    
    static getTypeCount() {
        return this.types.size;
    }
}

class Tree {
    constructor(x, y, type) {
        this.x = x;
        this.y = y;
        this.type = type;
    }
    
    draw() {
        this.type.draw(this.x, this.y);
    }
}

class Forest {
    constructor() {
        this.trees = [];
    }
    
    plantTree(x, y, name, color, texture) {
        const type = TreeFactory.getTreeType(name, color, texture);
        this.trees.push(new Tree(x, y, type));
    }
    
    draw() {
        this.trees.forEach(tree => tree.draw());
    }
}

const forest = new Forest();
forest.plantTree(0, 0, '松树', '绿色', '粗糙');
forest.plantTree(10, 20, '松树', '绿色', '粗糙');
forest.plantTree(30, 40, '枫树', '红色', '光滑');

console.log(`树种数量: ${TreeFactory.getTypeCount()}`);

东巴文小贴士

🔌 适配器 vs 装饰器 vs 代理

  • 适配器:改变接口,使不兼容的类能一起工作
  • 装饰器:不改变接口,动态添加职责
  • 代理:不改变接口,控制访问

它们都是"包装器"模式,但目的不同

📦 组合模式要点

组合模式让你可以一致地处理单个对象和组合对象:

  • 定义统一的组件接口
  • 叶子和组合都实现这个接口
  • 组合可以包含其他组合或叶子
  • 递归遍历整个树结构

下一步

下一章将探讨 行为型模式,学习对象之间的通信和职责分配模式。