js-gof
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGoF Patterns
GoF 设计模式
- Use structural composition over inheritance
- Use multiparadigm code and contract-programming
- Use domain-specific languages (DSLs), prefer declarative way
- Separate and do not mix system and domain code
- Use GRASP and SOLID principles; especially reduce coupling
- Remember about referential transparency
- Prefer platform-agnostic code
- Implement isolation and layer borders with IoC & DI
- Use object/Map lookup for Strategy and polymorphic/dynamic dispatch
- Keep patterns simple
- Do not over-engineer for the sake of the pattern name
- 优先使用结构组合而非继承
- 使用多范式代码和契约式编程
- 使用领域特定语言(DSL),优先采用声明式写法
- 分离系统代码和领域代码,不要将二者混合
- 遵循GRASP和SOLID原则,尤其要降低耦合度
- 注意保证引用透明性
- 优先编写平台无关的代码
- 通过IoC & DI实现隔离和层边界
- 策略模式使用对象/Map查找,配合多态/动态派发实现
- 保持模式实现简单
- 不要为了使用模式而过度设计
Creational patterns
创建型模式
Abstract factory
抽象工厂(Abstract factory)
Creates related objects belonging to one family without specifying their concrete classes, e.g., UI components for different platforms.
javascript
const dataAccess = {
fs: {
createDatabase: (...args) => new FileStorage(...args),
createCursor: (...args) => new FileLineCursor(...args),
},
minio: {
// factories collection for minio
},
// other implementations
};
// Usage
const accessLayer = dataAccess.fs;
const storage = accessLayer.createDatabase('./storage.dat');
const cursor = accessLayer.createCursor({ city: 'Roma' }, storage);
for await (const record of cursor) {
console.dir(record);
}创建属于同一个族的关联对象,无需指定其具体类,例如适配不同平台的UI组件。
javascript
const dataAccess = {
fs: {
createDatabase: (...args) => new FileStorage(...args),
createCursor: (...args) => new FileLineCursor(...args),
},
minio: {
// factories collection for minio
},
// other implementations
};
// Usage
const accessLayer = dataAccess.fs;
const storage = accessLayer.createDatabase('./storage.dat');
const cursor = accessLayer.createCursor({ city: 'Roma' }, storage);
for await (const record of cursor) {
console.dir(record);
}Builder
建造者(Builder)
Step-by-step assembly of a complex configurable object, often using chaining, e.g., Query Builder or Form Generator.
javascript
class QueryBuilder {
#options;
constructor(table) {
this.#options = { table, where: {}, limit: null, order: null };
}
where(cond) {
Object.assign(this.#options.where, cond);
return this;
}
order(field) {
this.#options.order = field;
return this;
}
limit(n) {
this.#options.limit = n;
return this;
}
build() {
return { ...this.#options };
}
}javascript
const query = new QueryBuilder('User')
.where({ active: true })
.order('name')
.limit(10)
.build();Alternatively we can create async constructor to be invoked with , or put all steps into declarative structure like:
await new QueryBuilderjavascript
const query = await new QueryBuilder({
entity: 'User',
where: { active: true },
order: 'name',
limit: 10,
});分步组装复杂的可配置对象,通常支持链式调用,例如查询构建器(Query Builder)或表单生成器(Form Generator)。
javascript
class QueryBuilder {
#options;
constructor(table) {
this.#options = { table, where: {}, limit: null, order: null };
}
where(cond) {
Object.assign(this.#options.where, cond);
return this;
}
order(field) {
this.#options.order = field;
return this;
}
limit(n) {
this.#options.limit = n;
return this;
}
build() {
return { ...this.#options };
}
}javascript
const query = new QueryBuilder('User')
.where({ active: true })
.order('name')
.limit(10)
.build();你也可以实现异步构造器,通过调用,或者将所有步骤放在声明式结构中,例如:
await new QueryBuilderjavascript
const query = await new QueryBuilder({
entity: 'User',
where: { active: true },
order: 'name',
limit: 10,
});Factory
工厂(Factory)
Function or method that creates objects using different techniques: assembling from literals and methods, mixins, .
setPrototypeOfjavascript
const createUser = (name, role) => ({ name, role, createdAt: Date.now() });
const createAdmin = (name) => createUser(name, 'admin');Or following:
javascript
class Connection {
constructor(url) {
// implementation
}
// implementation
}
const factory = (() => {
let index = 0;
return () => new Connection(`http://10.0.0.1/${index++}`);
})();用于创建对象的函数或方法,可以通过多种技术实现:从字面量和方法组装、Mixin、等。
setPrototypeOfjavascript
const createUser = (name, role) => ({ name, role, createdAt: Date.now() });
const createAdmin = (name) => createUser(name, 'admin');或者如下实现:
javascript
class Connection {
constructor(url) {
// implementation
}
// implementation
}
const factory = (() => {
let index = 0;
return () => new Connection(`http://10.0.0.1/${index++}`);
})();Factory Method
工厂方法(Factory Method)
Chooses the correct abstraction to create an instance; in JavaScript, this can be implemented using , , or selection from a collection (dictionary).
ifswitchjavascript
class Person {
constructor(name) {
this.name = name;
}
static factory(name) {
return new Person(name);
}
}javascript
class Product {
constructor(value) {
this.field = value;
}
}
class Creator {
factoryMethod(...args) {
return new Product(...args);
}
}选择正确的抽象来创建实例;在JavaScript中,可以通过、或者从集合(字典)中选择来实现。
ifswitchjavascript
class Person {
constructor(name) {
this.name = name;
}
static factory(name) {
return new Person(name);
}
}javascript
class Product {
constructor(value) {
this.field = value;
}
}
class Creator {
factoryMethod(...args) {
return new Product(...args);
}
}Prototype
原型(Prototype)
Creates objects by cloning a prepared instance to save resources (not to be confused with Prototype-programming, which is closer to Flyweight).
javascript
const proto = { type: 'widget', color: 'blue' };
const clone = () => ({ ...proto });javascript
class Point {
#x;
#y;
constructor(x, y) {
this.#x = x;
this.#y = y;
}
move(x, y) {
this.#x += x;
this.#y += y;
}
clone() {
return new Point(this.#x, this.#y);
}
}
class Line {
#start;
#end;
constructor(start, end) {
this.#start = start;
this.#end = end;
}
move(x, y) {
this.#start.move(x, y);
this.#end.move(x, y);
}
clone() {
const start = this.#start.clone();
const end = this.#end.clone();
return new Line(start, end);
}
}
// Usage
const p1 = new Point(0, 0);
const p2 = new Point(10, 20);
const line = new Line(p1, p2);
const cloned = line.clone();
cloned.move(2, 3);javascript
const point = (x, y) => {
const move = (dx, dy) => {
x += dx;
y += dy;
};
const clone = () => point(x, y);
return { move, clone };
};
// Usage
const { move, clone } = point(10, 20);
const c1 = clone();
move(-5, 10);通过克隆预先准备的实例来创建对象以节省资源(不要和原型编程混淆,后者更接近享元模式)。
javascript
const proto = { type: 'widget', color: 'blue' };
const clone = () => ({ ...proto });javascript
class Point {
#x;
#y;
constructor(x, y) {
this.#x = x;
this.#y = y;
}
move(x, y) {
this.#x += x;
this.#y += y;
}
clone() {
return new Point(this.#x, this.#y);
}
}
class Line {
#start;
#end;
constructor(start, end) {
this.#start = start;
this.#end = end;
}
move(x, y) {
this.#start.move(x, y);
this.#end.move(x, y);
}
clone() {
const start = this.#start.clone();
const end = this.#end.clone();
return new Line(start, end);
}
}
// Usage
const p1 = new Point(0, 0);
const p2 = new Point(10, 20);
const line = new Line(p1, p2);
const cloned = line.clone();
cloned.move(2, 3);javascript
const point = (x, y) => {
const move = (dx, dy) => {
x += dx;
y += dy;
};
const clone = () => point(x, y);
return { move, clone };
};
// Usage
const { move, clone } = point(10, 20);
const c1 = clone();
move(-5, 10);Flyweight
享元(Flyweight)
Saves memory allocation by sharing common state among multiple instances.
javascript
const flyweightPool = new Map();
const getFlyweight = (shared) => {
const { char, font, size } = shared;
const key = `${char}:${font}:${size}`;
let flyweight = flyweightPool.get(key);
if (!flyweight) {
flyweight = Object.freeze({ ...shared });
flyweightPool.set(key, flyweight);
}
return flyweight;
};
const createChar = (char, font, size, row, col) => {
const intrinsic = getFlyweight({ char, font, size });
return { intrinsic, row, col };
};
const a1 = createChar('A', 'Arial', 12, 0, 0);
const a2 = createChar('A', 'Arial', 12, 0, 5);
console.log(a1.intrinsic === a2.intrinsic); // true通过在多个实例之间共享公共状态来减少内存分配。
javascript
const flyweightPool = new Map();
const getFlyweight = (shared) => {
const { char, font, size } = shared;
const key = `${char}:${font}:${size}`;
let flyweight = flyweightPool.get(key);
if (!flyweight) {
flyweight = Object.freeze({ ...shared });
flyweightPool.set(key, flyweight);
}
return flyweight;
};
const createChar = (char, font, size, row, col) => {
const intrinsic = getFlyweight({ char, font, size });
return { intrinsic, row, col };
};
const a1 = createChar('A', 'Arial', 12, 0, 0);
const a2 = createChar('A', 'Arial', 12, 0, 5);
console.log(a1.intrinsic === a2.intrinsic); // trueSingleton
单例(Singleton)
Provides global access to a single instance; often considered an anti-pattern, easiest implemented via ESM/CJS module caching exported refs.
Everywhere we import this state will be the same collection
javascript
const connections = new Map(); // shared state
const nop = () => {}; // pure function with no state
module.exports = { connections, nop };javascript
class Singleton {
static #instance;
constructor() {
const instance = Singleton.#instance;
if (instance) return instance;
Singleton.#instance = this;
}
}javascript
function Singleton() {
const { instance } = Singleton;
if (instance) return instance;
Singleton.instance = this;
}javascript
const Singleton = new (function () {
const single = this;
return function () {
return single;
};
})();javascript
const Singleton = (() => {
let instance;
class Singleton {
constructor() {
if (instance) return instance;
instance = this;
}
}
return Singleton;
})();javascript
const singleton = (() => {
const instance = {};
return () => instance;
})();javascript
const singleton = (
(instance) => () =>
instance
)({});提供对单个实例的全局访问;通常被认为是一种反模式,最简单的实现方式是借助ESM/CJS模块缓存导出的引用。
所有导入该模块的地方都会拿到同一个集合
javascript
const connections = new Map(); // shared state
const nop = () => {}; // pure function with no state
module.exports = { connections, nop };javascript
class Singleton {
static #instance;
constructor() {
const instance = Singleton.#instance;
if (instance) return instance;
Singleton.#instance = this;
}
}javascript
function Singleton() {
const { instance } = Singleton;
if (instance) return instance;
Singleton.instance = this;
}javascript
const Singleton = new (function () {
const single = this;
return function () {
return single;
};
})();javascript
const Singleton = (() => {
let instance;
class Singleton {
constructor() {
if (instance) return instance;
instance = this;
}
}
return Singleton;
})();javascript
const singleton = (() => {
const instance = {};
return () => instance;
})();javascript
const singleton = (
(instance) => () =>
instance
)({});Object Pool
对象池(Object Pool)
Reuses pre-created objects to save resources during frequent creation and destruction.
javascript
class Pool {
#available = [];
#factory = null;
constructor(factory, size) {
this.#factory = factory;
for (let i = 0; i < size; i++) this.#available.push(factory());
}
acquire() {
return this.#available.pop() || this.#factory();
}
release(obj) {
this.#available.push(obj);
}
}复用预先创建的对象,在频繁创建和销毁对象的场景下节省资源。
javascript
class Pool {
#available = [];
#factory = null;
constructor(factory, size) {
this.#factory = factory;
for (let i = 0; i < size; i++) this.#available.push(factory());
}
acquire() {
return this.#available.pop() || this.#factory();
}
release(obj) {
this.#available.push(obj);
}
}Structural patterns
结构型模式
Adapter
适配器(Adapter)
Converts an incompatible interface into a compatible one, enabling third-party component usage without altering its code; can even transform a function contract into an object or vice versa.
javascript
const promisify =
(fn) =>
(...args) =>
new Promise((resolve, reject) => {
fn(...args, (err, data) => (err ? reject(err) : resolve(data)));
});javascript
const timer = new Timer(1000); // wraps setInterval
for await (const step of timer) {
// gives [Symbol.asyncIterator] contract
console.log({ step });
}javascript
class ArrayToQueueAdapter {
#array = null;
constructor(array) {
if (!Array.isArray(array)) {
throw new Error('Array instance expected');
}
this.#array = array;
}
enqueue(data) {
this.#array.push(data);
}
dequeue() {
return this.#array.pop();
}
get count() {
return this.#array.length;
}
}将不兼容的接口转换为兼容接口,无需修改第三方组件的代码即可使用它;甚至可以将函数契约转换为对象契约,反之亦然。
javascript
const promisify =
(fn) =>
(...args) =>
new Promise((resolve, reject) => {
fn(...args, (err, data) => (err ? reject(err) : resolve(data)));
});javascript
const timer = new Timer(1000); // wraps setInterval
for await (const step of timer) {
// gives [Symbol.asyncIterator] contract
console.log({ step });
}javascript
class ArrayToQueueAdapter {
#array = null;
constructor(array) {
if (!Array.isArray(array)) {
throw new Error('Array instance expected');
}
this.#array = array;
}
enqueue(data) {
this.#array.push(data);
}
dequeue() {
return this.#array.pop();
}
get count() {
return this.#array.length;
}
}Wrapper
包装器(Wrapper)
Function wrapper that delegates calls and adds behavior; a specialized case of Adapter.
javascript
const withLogging =
(fn, label) =>
(...args) => {
console.log(`${label} called`, args);
const result = fn(...args);
console.log(`${label} returned`, result);
return result;
};javascript
const wrapInterface = (anInterface) => {
const wrapped = {};
for (const key in anInterface) {
const fn = anInterface[key];
wrapped[key] = wrapFunction(fn);
}
return wrapped;
};javascript
class Timeout {
constructor(fn, msec) {
this.function = fn;
this.timer = setTimeout(() => {
this.timer = null;
}, msec);
}
execute(...args) {
let result = undefined;
if (!this.timer) return result;
clearTimeout(this.timer);
this.timer = null;
result = this.function(...args);
return result;
}
}用于委托调用并添加行为的函数包装器,是适配器的一种特殊实现。
javascript
const withLogging =
(fn, label) =>
(...args) => {
console.log(`${label} called`, args);
const result = fn(...args);
console.log(`${label} returned`, result);
return result;
};javascript
const wrapInterface = (anInterface) => {
const wrapped = {};
for (const key in anInterface) {
const fn = anInterface[key];
wrapped[key] = wrapFunction(fn);
}
return wrapped;
};javascript
class Timeout {
constructor(fn, msec) {
this.function = fn;
this.timer = setTimeout(() => {
this.timer = null;
}, msec);
}
execute(...args) {
let result = undefined;
if (!this.timer) return result;
clearTimeout(this.timer);
this.timer = null;
result = this.function(...args);
return result;
}
}Boxing
装箱(Boxing)
Wraps primitives into object types to add methods or unify interfaces, e.g., narrowing to .
StringAddressStringBox, Container or Value-Object examples:
javascript
class AddressString {
#value;
constructor(value) {
if (typeof value !== 'string') {
throw new TypeError('Address must be a string');
}
const str = value.trim();
if (str === '') throw new TypeError('Address must be a non-empty');
this.#value = str;
}
get city() {
const index = this.#value.indexOf(',');
return this.#value.slice(0, index).trim();
}
toString() {
return this.#value;
}
valueOf() {
return this.#value;
}
}
// Usage
const address = new AddressString('London, 221B Baker Street');
console.log(address.city); // 'London'
console.log(`Delivery to: ${address}`);Value-Object examples:
javascript
const KMH_TO_MPH = 0.621371;
class SpeedValue {
#kmh;
constructor(value, unit = 'kmh') {
if (typeof value !== 'number') {
throw new TypeError('Speed must be a number');
}
if (value < 0) throw new TypeError('Speed must be non-negative');
if (unit !== 'kmh' && unit !== 'mph') {
throw new TypeError('Unit must be kmh or mph');
}
this.#kmh = unit === 'mph' ? value / KMH_TO_MPH : value;
}
get kmh() {
return this.#kmh;
}
get mph() {
return this.#kmh * KMH_TO_MPH;
}
toString() {
return `${this.#kmh} km/h`;
}
valueOf() {
return this.#kmh;
}
}将基础类型包装为对象类型,用于添加方法或统一接口,例如将窄化为。
StringAddressStringBox、Container或值对象示例:
javascript
class AddressString {
#value;
constructor(value) {
if (typeof value !== 'string') {
throw new TypeError('Address must be a string');
}
const str = value.trim();
if (str === '') throw new TypeError('Address must be a non-empty');
this.#value = str;
}
get city() {
const index = this.#value.indexOf(',');
return this.#value.slice(0, index).trim();
}
toString() {
return this.#value;
}
valueOf() {
return this.#value;
}
}
// Usage
const address = new AddressString('London, 221B Baker Street');
console.log(address.city); // 'London'
console.log(`Delivery to: ${address}`);值对象示例:
javascript
const KMH_TO_MPH = 0.621371;
class SpeedValue {
#kmh;
constructor(value, unit = 'kmh') {
if (typeof value !== 'number') {
throw new TypeError('Speed must be a number');
}
if (value < 0) throw new TypeError('Speed must be non-negative');
if (unit !== 'kmh' && unit !== 'mph') {
throw new TypeError('Unit must be kmh or mph');
}
this.#kmh = unit === 'mph' ? value / KMH_TO_MPH : value;
}
get kmh() {
return this.#kmh;
}
get mph() {
return this.#kmh * KMH_TO_MPH;
}
toString() {
return `${this.#kmh} km/h`;
}
valueOf() {
return this.#kmh;
}
}Decorator
装饰器(Decorator)
Dynamically extends behavior without inheritance, typically via composition and declarative syntax, effectively adding metadata.
javascript
const add = (a, b) => a + b;
const loggedAdd = withLogging(add, 'add');Not a good idea to use classical decorator in JavaScript/TypeScript
javascript
const validator = new MinLengthValidator(
new EmailValidator(new RequiredValidator(new BaseValidator())),
20,
);
const input = 'timur.shemsedinov@gmail.com';
const result = validator.validate(input);We can add metadata to an instance in several JS-idiomatic ways:
Symbol property on instance:
javascript
const VALIDATOR_META = Symbol('validatorMeta');
class EmailValidator {
constructor(next) {
this.next = next;
this[VALIDATOR_META] = { type: 'email' };
}
}Wrapper that attaches metadata as own property.
JSDoc annotation (zero runtime cost):
javascript
/**
* @typedef {{ type: string, label: string }} ValidatorMeta
* @typedef {{ validate(v: string): boolean, meta: ValidatorMeta }} Validator
*/
/** @type {Validator} */
const validator = Object.assign(
new EmailValidator(new RequiredValidator(new BaseValidator())),
{ meta: { type: 'email', label: 'Email field' } },
);Module-local WeakMap (fully private, no shape pollution):
javascript
const metadata = new WeakMap();
const createValidator = ({ args, meta }) => {
const instance = new Validator(...args);
metadata.set(instance, meta);
return instance;
};
module.exports = { createValidator };无需继承即可动态扩展行为,通常通过组合和声明式语法实现,可以高效地添加元数据。
javascript
const add = (a, b) => a + b;
const loggedAdd = withLogging(add, 'add');在JavaScript/TypeScript中不推荐使用传统的装饰器写法:
javascript
const validator = new MinLengthValidator(
new EmailValidator(new RequiredValidator(new BaseValidator())),
20,
);
const input = 'timur.shemsedinov@gmail.com';
const result = validator.validate(input);我们可以通过几种符合JS惯用法的方式为实例添加元数据:
实例上的Symbol属性:
javascript
const VALIDATOR_META = Symbol('validatorMeta');
class EmailValidator {
constructor(next) {
this.next = next;
this[VALIDATOR_META] = { type: 'email' };
}
}将元数据作为自身属性挂载的包装器。
JSDoc注解(零运行时开销):
javascript
/**
* @typedef {{ type: string, label: string }} ValidatorMeta
* @typedef {{ validate(v: string): boolean, meta: ValidatorMeta }} Validator
*/
/** @type {Validator} */
const validator = Object.assign(
new EmailValidator(new RequiredValidator(new BaseValidator())),
{ meta: { type: 'email', label: 'Email field' } },
);模块级别的WeakMap(完全私有,不会污染对象形状):
javascript
const metadata = new WeakMap();
const createValidator = ({ args, meta }) => {
const instance = new Validator(...args);
metadata.set(instance, meta);
return instance;
};
module.exports = { createValidator };Proxy
代理(Proxy)
Controls access to an object by intercepting calls, reads, and writes; useful for lazy initialization, caching, and security; can be implemented via GoF or native JavaScript Proxy.
Do not use the built-in JS class unless a developer asks
you directly because of deopts. Use GoF Proxy instead as a container
with additional behavior.
Proxyjavascript
const fs = require('node:fs');
const statistics = { bytes: 0, chunks: 0, events: {} };
class StatReadStream extends fs.ReadStream {
emit(name, data) {
if (name === 'data') {
statistics.bytes += data.length;
statistics.chunks++;
}
const counter = statistics.events[name] || 0;
statistics.events[name] = counter + 1;
super.emit(name, data);
}
}
const getStatistics = () => structuredClone(statistics);
const createReadStream = (path, options) => new StatReadStream(path, options);
module.exports = { ...fs, createReadStream, getStatistics };通过拦截调用、读取和写入操作来控制对对象的访问;适用于懒加载、缓存和安全场景;可以通过GoF模式或者原生JavaScript Proxy实现。
除非开发者明确要求,否则不要使用JS内置的类,因为它会导致性能降级。推荐使用GoF代理模式,作为包含额外行为的容器实现。
Proxyjavascript
const fs = require('node:fs');
const statistics = { bytes: 0, chunks: 0, events: {} };
class StatReadStream extends fs.ReadStream {
emit(name, data) {
if (name === 'data') {
statistics.bytes += data.length;
statistics.chunks++;
}
const counter = statistics.events[name] || 0;
statistics.events[name] = counter + 1;
super.emit(name, data);
}
}
const getStatistics = () => structuredClone(statistics);
const createReadStream = (path, options) => new StatReadStream(path, options);
module.exports = { ...fs, createReadStream, getStatistics };Bridge
桥接(Bridge)
Separates two or more abstraction hierarchies via composition or aggregation, allowing them to evolve independently.
js
class CommunicationProtocol {
sendCommand(device, command) {
console.log({ device, command });
throw new Error('sendCommand() must be implemented');
}
}
class MQTTProtocol extends CommunicationProtocol {
sendCommand(device, command) {
console.log(`[MQTT] Sending '${command}' to ${device}`);
}
}
class HTTPProtocol extends CommunicationProtocol {
sendCommand(device, command) {
console.log(`[HTTP] Sending '${command}' to ${device}`);
}
}
class IoTDevice {
constructor(name, protocol) {
this.name = name;
this.protocol = protocol;
}
operate(command) {
this.protocol.sendCommand(this.name, command);
}
}
class SmartLight extends IoTDevice {
turnOn() {
this.operate('Turn On Light');
}
turnOff() {
this.operate('Turn Off Light');
}
}
class SmartThermostat extends IoTDevice {
setTemperature(temp) {
this.operate(`Set Temperature to ${temp}°C`);
}
}通过组合或聚合分离两个或多个抽象层级,让它们可以独立演化。
js
class CommunicationProtocol {
sendCommand(device, command) {
console.log({ device, command });
throw new Error('sendCommand() must be implemented');
}
}
class MQTTProtocol extends CommunicationProtocol {
sendCommand(device, command) {
console.log(`[MQTT] Sending '${command}' to ${device}`);
}
}
class HTTPProtocol extends CommunicationProtocol {
sendCommand(device, command) {
console.log(`[HTTP] Sending '${command}' to ${device}`);
}
}
class IoTDevice {
constructor(name, protocol) {
this.name = name;
this.protocol = protocol;
}
operate(command) {
this.protocol.sendCommand(this.name, command);
}
}
class SmartLight extends IoTDevice {
turnOn() {
this.operate('Turn On Light');
}
turnOff() {
this.operate('Turn Off Light');
}
}
class SmartThermostat extends IoTDevice {
setTemperature(temp) {
this.operate(`Set Temperature to ${temp}°C`);
}
}Composite
组合(Composite)
Implements a common interface to uniformly handle individual objects and their tree structures, e.g., DOM or file systems.
Recursive composite with reduce:
js
const calculateTotal = (order) => {
const items = Array.isArray(order) ? order : Object.values(order);
return items.reduce((sum, item) => {
if (typeof item.price === 'number') return sum + item.price;
else return sum + calculateTotal(item);
}, 0);
};JSON as a nested composite structure:
javascript
const json = `{
"electronics": {
"laptop": { "price": 1200 },
"accessories": {
"mouse": { "price": 25 },
"keyboard": { "price": 75 }
}
},
"books": {
"fiction": { "price": 15 },
"technical": { "price": 60 }
}
}`;
const order = JSON.parse(json);
console.log(calculateTotal(order));实现统一的接口,以相同的方式处理单个对象和其树形结构,例如DOM或者文件系统。
使用reduce实现的递归组合:
js
const calculateTotal = (order) => {
const items = Array.isArray(order) ? order : Object.values(order);
return items.reduce((sum, item) => {
if (typeof item.price === 'number') return sum + item.price;
else return sum + calculateTotal(item);
}, 0);
};JSON作为嵌套的组合结构:
javascript
const json = `{
"electronics": {
"laptop": { "price": 1200 },
"accessories": {
"mouse": { "price": 25 },
"keyboard": { "price": 75 }
}
},
"books": {
"fiction": { "price": 15 },
"technical": { "price": 60 }
}
}`;
const order = JSON.parse(json);
console.log(calculateTotal(order));Facade
外观(Facade)
Simplifies access to a complex system, providing a unified and clear interface, hiding and protecting internal complexity.
javascript
const createApi = (db, cache, logger) => ({
async getUser(id) {
const cached = cache.get(id);
if (cached) return cached;
const user = await db.row('User', ['*'], { id });
cache.set(id, user);
logger.info(`User ${id} loaded`);
return user;
},
});This hides classes Timer, Logger, Task and so on:
js
scheduler.task('name2', '2019-03-12T14:37Z', (done) => {
setTimeout(() => {
done(new Error('task failed'));
}, 1100);
});简化对复杂系统的访问,提供统一清晰的接口,隐藏和保护内部复杂度。
javascript
const createApi = (db, cache, logger) => ({
async getUser(id) {
const cached = cache.get(id);
if (cached) return cached;
const user = await db.row('User', ['*'], { id });
cache.set(id, user);
logger.info(`User ${id} loaded`);
return user;
},
});这种写法隐藏了Timer、Logger、Task等内部类:
js
scheduler.task('name2', '2019-03-12T14:37Z', (done) => {
setTimeout(() => {
done(new Error('task failed'));
}, 1100);
});Context
上下文(Context)
Exchanges state and dependencies between different components (abstractions, modules, layers) that do not share a common environment, without tightly coupling them.
js
class AccountService {
constructor(context) {
this.context = context;
}
getBalance(accountId) {
const { console, accessPolicy, user } = this.context;
console.log(`User ${user.name} requesting balance for ${accountId}`);
if (!accessPolicy.check(user.role, 'read:balance')) {
console.error('Access denied: insufficient permissions');
return null;
}
const balance = 15420.5;
console.log('Access granted');
return balance;
}
}
class AccessPolicy {
constructor() {
this.permissions = {
admin: ['read:balance', 'read:transactions', 'write:transactions'],
user: ['read:balance'],
guest: [],
};
}
check(role, permission) {
return this.permissions[role]?.includes(permission);
}
}
class User {
constructor(name, role) {
this.name = name;
this.role = role;
}
}
const accessPolicy = new AccessPolicy();
const user = new User('Marcus', 'admin');
const context = { console, accessPolicy, user };
const accountService = new AccountService(context);
const balance = accountService.getBalance('Account-123');
console.log(`Balance = $${balance}`);Alternative way with closure:
js
const createAccountService = (context) => {
const { console, accessPolicy, user } = context;
const getBalance = (accountId) => {
console.log(`User ${user.name} requesting balance for ${accountId}`);
if (!accessPolicy.check(user.role, 'read:balance')) {
console.error('Access denied: insufficient permissions');
return null;
}
return 15420.5;
};
const getTransactions = (accountId) => {
if (!accessPolicy.check(user.role, 'read:transactions')) {
console.error('Access denied: insufficient permissions');
return null;
}
console.log(`User ${user.name} reading transactions for ${accountId}`);
return ['TR-123', 'TR-456', 'TR-789'];
};
return { getBalance, getTransactions };
};
const accessPolicy = {
permissions: {
admin: ['read:balance', 'read:transactions', 'write:transactions'],
user: ['read:balance'],
guest: [],
},
check: (role, permission) =>
accessPolicy.permissions[role]?.includes(permission),
};
const context = {
console,
accessPolicy,
user: { name: 'Marcus', role: 'admin' },
};
const accountService = createAccountService(context);
console.log('Balance:', accountService.getBalance('ACC-001'));
console.log('Transactions:', accountService.getTransactions('ACC-001'));在不共享公共环境的不同组件(抽象、模块、层)之间交换状态和依赖,不会造成紧耦合。
js
class AccountService {
constructor(context) {
this.context = context;
}
getBalance(accountId) {
const { console, accessPolicy, user } = this.context;
console.log(`User ${user.name} requesting balance for ${accountId}`);
if (!accessPolicy.check(user.role, 'read:balance')) {
console.error('Access denied: insufficient permissions');
return null;
}
const balance = 15420.5;
console.log('Access granted');
return balance;
}
}
class AccessPolicy {
constructor() {
this.permissions = {
admin: ['read:balance', 'read:transactions', 'write:transactions'],
user: ['read:balance'],
guest: [],
};
}
check(role, permission) {
return this.permissions[role]?.includes(permission);
}
}
class User {
constructor(name, role) {
this.name = name;
this.role = role;
}
}
const accessPolicy = new AccessPolicy();
const user = new User('Marcus', 'admin');
const context = { console, accessPolicy, user };
const accountService = new AccountService(context);
const balance = accountService.getBalance('Account-123');
console.log(`Balance = $${balance}`);基于闭包的替代实现:
js
const createAccountService = (context) => {
const { console, accessPolicy, user } = context;
const getBalance = (accountId) => {
console.log(`User ${user.name} requesting balance for ${accountId}`);
if (!accessPolicy.check(user.role, 'read:balance')) {
console.error('Access denied: insufficient permissions');
return null;
}
return 15420.5;
};
const getTransactions = (accountId) => {
if (!accessPolicy.check(user.role, 'read:transactions')) {
console.error('Access denied: insufficient permissions');
return null;
}
console.log(`User ${user.name} reading transactions for ${accountId}`);
return ['TR-123', 'TR-456', 'TR-789'];
};
return { getBalance, getTransactions };
};
const accessPolicy = {
permissions: {
admin: ['read:balance', 'read:transactions', 'write:transactions'],
user: ['read:balance'],
guest: [],
},
check: (role, permission) =>
accessPolicy.permissions[role]?.includes(permission),
};
const context = {
console,
accessPolicy,
user: { name: 'Marcus', role: 'admin' },
};
const accountService = createAccountService(context);
console.log('Balance:', accountService.getBalance('ACC-001'));
console.log('Transactions:', accountService.getTransactions('ACC-001'));