mcpField
J
js-gof

js-gof

github.com/metarhia开发6048 次安装12 周前更新
代码仓库

关于 js-gof

js-gof 是面向 JavaScript 开发者的 GoF 模式技能,结合组合、SOLID 和低耦合等原则提供指导。内容涵盖创建型与结构型模式,并配有代码示例。

主要功能

  1. 01创建型模式指导
  2. 02结构型模式指导
  3. 03JavaScript 代码示例
  4. 04强调组合与低耦合

适用场景

  1. 01选择对象创建模式
  2. 02适配不兼容的接口
  3. 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'));

评论

支持 Markdown