关于 js-gof
js-gof 是面向 JavaScript 开发者的 GoF 模式技能,结合组合、SOLID 和低耦合等原则提供指导。内容涵盖创建型与结构型模式,并配有代码示例。
主要功能
- 01创建型模式指导
- 02结构型模式指导
- 03JavaScript 代码示例
- 04强调组合与低耦合
适用场景
- 01选择对象创建模式
- 02适配不兼容的接口
- 03构建低耦合的 JavaScript 系统
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ metarhia/ metaskills --skill js-gof安装后在 Claude Code 中输入/js-gof确认已加载。
详细介绍
GoF 模式
- 优先使用结构组合,而非继承
- 使用多范式代码和契约式编程
- 使用领域特定语言(DSL),优先采用声明式方式
- 将系统代码与领域代码分离,不要混用
- 遵循 GRASP 和 SOLID 原则;尤其要降低耦合
- 牢记引用透明性
- 优先编写与平台无关的代码
- 使用 IoC 和 DI 实现隔离与层边界
- 使用对象/Map 查找来实现策略以及多态/动态分派
- 保持模式简单
- 不要为了模式之名而过度设计
创建型模式
抽象工厂
创建属于同一系列的相关对象,而无需指定其具体类,例如,为不同平台创建 UI 组件。
参考:https://github.com/HowProgrammingWorks/AbstractFactory
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);
}
建造者
逐步组装复杂的可配置对象,通常使用链式调用,例如查询构建器或表单生成器。
参考:https://github.com/HowProgrammingWorks/Builder
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 };
}
}
const query = new QueryBuilder('User')
.where({ active: true })
.order('name')
.limit(10)
.build();
或者,我们可以创建异步构造函数,并通过 await new QueryBuilder 调用;也可以将所有步骤放入如下的声明式结构中:
const query = await new QueryBuilder({
entity: 'User',
where: { active: true },
order: 'name',
limit: 10,
});
工厂
用于创建对象的函数或方法,可采用不同方式:从字面量和方法组装、使用混入、setPrototypeOf。
参考:https://github.com/HowProgrammingWorks/Factory
或者采用以下方式:
const createUser = (name, role) => ({ name, role, createdAt: Date.now() });
const createAdmin = (name) => createUser(name, 'admin');
class Connection {
constructor(url) {
// implementation
}
// implementation
}
const factory = (() => {
let index = 0;
return () => new Connection(`http://10.0.0.1/${index++}`);
})();
工厂方法
选择合适的抽象来创建实例;在 JavaScript 中,可以通过 if、switch 或从集合(字典)中选择来实现。
参考:https://github.com/HowProgrammingWorks/FactoryMethod
class Person {
constructor(name) {
this.name = name;
}
static factory(name) {
return new Person(name);
}
}
class Product {
constructor(value) {
this.field = value;
}
}
class Creator {
factoryMethod(...args) {
return new Product(...args);
}
}
原型
通过克隆预先准备好的实例来创建对象,从而节省资源(不要与原型编程混淆,后者更接近享元模式)。
参考:https://github.com/HowProgrammingWorks/PrototypePattern
const proto = { type: 'widget', color: 'blue' };
const clone = () => ({ ...proto });
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);
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);
享元
通过在多个实例间共享公共状态来减少内存分配。
参考:https://github.com/HowProgrammingWorks/Flyweight
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
单例
提供对单个实例的全局访问;它通常被视为反模式,最简单的实现方式是利用 ESM/CJS 模块缓存导出的引用。
参考:https://github.com/HowProgrammingWorks/Singleton
无论在哪里导入此状态,得到的都是同一个集合
const connections = new Map(); // shared state
const nop = () => {}; // pure function with no state
module.exports = { connections, nop };
class Singleton {
static #instance;
constructor() {
const instance = Singleton.#instance;
if (instance) return instance;
Singleton.#instance = this;
}
}
function Singleton() {
const { instance } = Singleton;
if (instance) return instance;
Singleton.instance = this;
}
const Singleton = new (function () {
const single = this;
return function () {
return single;
};
})();
const Singleton = (() => {
let instance;
class Singleton {
constructor() {
if (instance) return instance;
instance = this;
}
}
return Singleton;
})();
const singleton = (() => {
const instance = {};
return () => instance;
})();
const singleton = (
(instance) => () =>
instance
)({});
对象池
重用预先创建的对象,以节省频繁创建和销毁对象时消耗的资源。
参考:https://github.com/HowProgrammingWorks/Pool
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);
}
}
结构型模式
适配器
将不兼容的接口转换为兼容接口,使其能够使用第三方组件而无需修改其代码;甚至可以将函数契约转换为对象,反之亦然。
参考:https://github.com/HowProgrammingWorks/Adapter
const promisify =
(fn) =>
(...args) =>
new Promise((resolve, reject) => {
fn(...args, (err, data) => (err ? reject(err) : resolve(data)));
});
const timer = new Timer(1000); // wraps setInterval
for await (const step of timer) {
// gives [Symbol.asyncIterator] contract
console.log({ step });
}
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;
}
}
包装器
委托调用并添加行为的函数包装器;是适配器的一种特例。
参考:https://github.com/HowProgrammingWorks/Wrapper
const withLogging =
(fn, label) =>
(...args) => {
console.log(`${label} called`, args);
const result = fn(...args);
console.log(`${label} returned`, result);
return result;
};
const wrapInterface = (anInterface) => {
const wrapped = {};
for (const key in anInterface) {
const fn = anInterface[key];
wrapped[key] = wrapFunction(fn);
}
return wrapped;
};
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;
}
}
装箱
将原始值包装为对象类型,以添加方法或统一接口,例如将 String 缩窄为 AddressString。
参考:https://github.com/HowProgrammingWorks/ADT
装箱、容器或值对象示例:
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}`);
值对象示例:
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;
}
}
装饰器
无需继承即可动态扩展行为,通常通过组合和声明式语法实现,实质上是添加元数据。
参考:https://github.com/HowProgrammingWorks/Decorator
const add = (a, b) => a + b;
const loggedAdd = withLogging(add, 'add');
在 JavaScript/TypeScript 中使用经典装饰器不是个好主意
const validator = new MinLengthValidator(
new EmailValidator(new RequiredValidator(new BaseValidator())),
20,
);
const input = '[email protected]';
const result = validator.validate(input);
我们可以通过几种符合 JS 惯用风格的方式向实例添加元数据:
实例上的 Symbol 属性:
const VALIDATOR_META = Symbol('validatorMeta');
class EmailValidator {
constructor(next) {
this.next = next;
this[VALIDATOR_META] = { type: 'email' };
}
}
将元数据作为自有属性附加到实例的包装器。
JSDoc 注解(运行时开销为零):
/**
* @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(完全私有,不会污染对象结构):
const metadata = new WeakMap();
const createValidator = ({ args, meta }) => {
const instance = new Validator(...args);
metadata.set(instance, meta);
return instance;
};
module.exports = { createValidator };
代理
通过拦截调用、读取和写入来控制对对象的访问;适用于延迟初始化、缓存和安全防护;可通过 GoF 代理或原生 JavaScript Proxy 实现。
参考:https://github.com/HowProgrammingWorks/Proxy
除非开发者直接要求,否则不要使用内置的 JS Proxy 类,
因为它会导致去优化。请改用 GoF 代理,将其作为具有额外行为的容器。
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 };
桥接
通过组合或聚合分离两个或多个抽象层次,使它们能够独立演进。
参考:https://github.com/HowProgrammingWorks/Bridge
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`);
}
}
组合
实现通用接口,以统一处理单个对象及其树形结构,例如 DOM 或文件系统。
参考:https://github.com/HowProgrammingWorks/Composite
使用 reduce 的递归组合:
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:
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));
外观
简化对复杂系统的访问,提供统一且清晰的接口,隐藏并保护内部复杂性。
参考:https://github.com/HowProgrammingWorks/Facade
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 等类:
scheduler.task('name2', '2019-03-12T14:37Z', (done) => {
setTimeout(() => {
done(new Error('task failed'));
}, 1100);
});
上下文
在不共享通用环境的不同组件(抽象、模块、层)之间交换状态和依赖关系,同时避免紧密耦合。
参考:https://github.com/HowProgrammingWorks/Context
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}`);
使用闭包的另一种方式:
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'));