结构型设计模式关注类和对象的组合,通过继承或组合来构建更大的结构。本章将介绍JavaScript中常用的结构型设计模式。
适配器模式将一个类的接口转换成客户端期望的另一个接口,使原本不兼容的类可以一起工作。
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 };
}
}
装饰器模式动态地给对象添加额外的职责,比生成子类更为灵活。
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));
}
}
};
}
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}`);
}
}
代理模式为其他对象提供代理以控制对这个对象的访问。
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);
});
}
}
外观模式为子系统中的一组接口提供一个统一的入口。
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();
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();
}
}
组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。
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`);
桥接模式将抽象部分与实现部分分离,使它们都可以独立变化。
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());
享元模式通过共享对象来最小化内存使用。
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 代理
- 适配器:改变接口,使不兼容的类能一起工作
- 装饰器:不改变接口,动态添加职责
- 代理:不改变接口,控制访问
它们都是"包装器"模式,但目的不同
📦 组合模式要点
组合模式让你可以一致地处理单个对象和组合对象:
- 定义统一的组件接口
- 叶子和组合都实现这个接口
- 组合可以包含其他组合或叶子
- 递归遍历整个树结构
下一章将探讨 行为型模式,学习对象之间的通信和职责分配模式。