factory-function-composition

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Factory Function Composition

工厂函数组合

This skill helps you apply factory function patterns for clean dependency injection and function composition in TypeScript.
本技能将帮助你在TypeScript中应用工厂函数模式,实现清晰的依赖注入与函数组合。

When to Apply This Skill

何时应用本技能

Use this pattern when you see:
  • A function that takes a client/resource as its first argument
  • Options from different layers (client, service, method) mixed together
  • Client creation happening inside functions that shouldn't own it
  • Functions that are hard to test because they create their own dependencies
当你遇到以下场景时,可使用该模式:
  • 函数将客户端/资源作为第一个参数
  • 来自不同层级(客户端、服务、方法)的配置混杂在一起
  • 客户端的创建发生在不应拥有该逻辑的函数内部
  • 函数因自行创建依赖而难以测试

The Universal Signature

通用签名

Every factory function follows this signature:
typescript
function createSomething(dependencies, options?) {
	return {
		/* methods */
	};
}
  • First argument: Always the resource(s). Either a single client or a destructured object of multiple dependencies.
  • Second argument: Optional configuration specific to this factory. Never client config—that belongs at client creation.
Two arguments max. First is resources, second is config. No exceptions.
所有工厂函数均遵循以下签名:
typescript
function createSomething(dependencies, options?) {
	return {
		/* methods */
	};
}
  • 第一个参数:始终是资源。可以是单个客户端,也可以是包含多个依赖的解构对象。
  • 第二个参数:可选的、针对当前工厂的配置。绝不包含客户端配置——客户端配置应在创建客户端时指定。
最多两个参数。第一个是资源,第二个是配置。无例外。

The Core Pattern

核心模式

typescript
// Single dependency
function createService(client, options = {}) {
	return {
		method(methodOptions) {
			// Uses client, options, and methodOptions
		},
	};
}

// Multiple dependencies
function createService({ db, cache }, options = {}) {
	return {
		method(methodOptions) {
			// Uses db, cache, options, and methodOptions
		},
	};
}

// Usage
const client = createClient(clientOptions);
const service = createService(client, serviceOptions);
service.method(methodOptions);
typescript
// 单个依赖
function createService(client, options = {}) {
	return {
		method(methodOptions) {
			// 使用client、options和methodOptions
		},
	};
}

// 多个依赖
function createService({ db, cache }, options = {}) {
	return {
		method(methodOptions) {
			// 使用db、cache、options和methodOptions
		},
	};
}

// 使用示例
const client = createClient(clientOptions);
const service = createService(client, serviceOptions);
service.method(methodOptions);

Key Principles

核心原则

  1. Client configuration belongs at client creation time — don't pipe clientOptions through your factory
  2. Each layer has its own options — client, service, and method options stay separate
  3. Dependencies come first — factory functions take dependencies as the first argument
  4. Return objects with methods — not standalone functions that need the resource passed in
  1. 客户端配置应在创建客户端时指定 —— 不要将客户端配置通过工厂传递
  2. 每个层级有独立的配置 —— 客户端、服务与方法的配置需保持分离
  3. 依赖优先 —— 工厂函数将依赖作为第一个参数
  4. 返回包含方法的对象 —— 而非需要传入资源的独立函数

Recognizing the Anti-Patterns

识别反模式

Anti-Pattern 1: Function takes client as first argument

反模式1:函数将客户端作为第一个参数

typescript
// Bad
function doSomething(client, options) { ... }
doSomething(client, options);

// Good
const service = createService(client);
service.doSomething(options);
typescript
// 不良示例
function doSomething(client, options) { ... }
doSomething(client, options);

// 优化后
const service = createService(client);
service.doSomething(options);

Anti-Pattern 2: Client creation hidden inside

反模式2:客户端创建逻辑被隐藏

typescript
// Bad
function doSomething(clientOptions, methodOptions) {
	const client = createClient(clientOptions); // Hidden!
	// ...
}

// Good
const client = createClient(clientOptions);
const service = createService(client);
service.doSomething(methodOptions);
typescript
// 不良示例
function doSomething(clientOptions, methodOptions) {
	const client = createClient(clientOptions); // 隐藏逻辑!
	// ...
}

// 优化后
const client = createClient(clientOptions);
const service = createService(client);
service.doSomething(methodOptions);

Anti-Pattern 3: Mixed options blob

反模式3:配置混杂成大对象

typescript
// Bad
doSomething({
	timeout: 5000, // Client option
	retries: 3, // Client option
	endpoint: '/users', // Method option
	payload: data, // Method option
});

// Good
const client = createClient({ timeout: 5000, retries: 3 });
const service = createService(client);
service.doSomething({ endpoint: '/users', payload: data });
typescript
// 不良示例
doSomething({
	timeout: 5000, // 客户端配置
	retries: 3, // 客户端配置
	endpoint: '/users', // 方法配置
	payload: data, // 方法配置
});

// 优化后
const client = createClient({ timeout: 5000, retries: 3 });
const service = createService(client);
service.doSomething({ endpoint: '/users', payload: data });

Anti-Pattern 4: Multiple layers hidden

反模式4:多层级逻辑被隐藏

typescript
// Bad
function doSomething(clientOptions, serviceOptions, methodOptions) {
	const client = createClient(clientOptions);
	const service = createService(client, serviceOptions);
	return service.method(methodOptions);
}

// Good — each layer visible and configurable
const client = createClient(clientOptions);
const service = createService(client, serviceOptions);
service.method(methodOptions);
typescript
// 不良示例
function doSomething(clientOptions, serviceOptions, methodOptions) {
	const client = createClient(clientOptions);
	const service = createService(client, serviceOptions);
	return service.method(methodOptions);
}

// 优化后 —— 每个层级可见且可配置
const client = createClient(clientOptions);
const service = createService(client, serviceOptions);
service.method(methodOptions);

Multiple Dependencies

多依赖场景

When your service needs multiple clients:
typescript
function createService(
	{ db, cache, http }, // Dependencies as destructured object
	options = {}, // Service options
) {
	return {
		method(methodOptions) {
			// Uses db, cache, http
		},
	};
}

// Usage
const db = createDbConnection(dbOptions);
const cache = createCacheClient(cacheOptions);
const http = createHttpClient(httpOptions);

const service = createService({ db, cache, http }, serviceOptions);
service.method(methodOptions);
当你的服务需要多个客户端时:
typescript
function createService(
	{ db, cache, http }, // 依赖以解构对象传入
	options = {}, // 服务配置
) {
	return {
		method(methodOptions) {
			// 使用db、cache、http
		},
	};
}

// 使用示例
const db = createDbConnection(dbOptions);
const cache = createCacheClient(cacheOptions);
const http = createHttpClient(httpOptions);

const service = createService({ db, cache, http }, serviceOptions);
service.method(methodOptions);

The Mental Model

思维模型

Think of it as a chain where each link:
  • Receives a resource from the previous link
  • Adds its own configuration
  • Produces something for the next link
createClient(...)  →  createService(client, ...)  →  service.method(...)
     ↑                       ↑                            ↑
 clientOptions          serviceOptions              methodOptions
可以将其视为一条链条,每个环节:
  • 接收上一个环节的资源
  • 添加自身的配置
  • 为下一个环节生成产物
createClient(...)  →  createService(client, ...)  →  service.method(...)
     ↑                       ↑                            ↑
 clientOptions          serviceOptions              methodOptions

Benefits

优势

  • Testability: Inject mock clients easily
  • Reusability: Share clients across multiple services
  • Flexibility: Configure each layer independently
  • Clarity: Clear ownership of configuration at each level
  • 可测试性:轻松注入模拟客户端
  • 可复用性:在多个服务间共享客户端
  • 灵活性:独立配置每个层级
  • 清晰性:每个层级的配置归属明确

References

参考资料

See the full articles for more details:
  • The Universal Factory Function Signature — signature explained in depth
  • Stop Passing Clients as Arguments — practical guide
  • The Factory Function Pattern — detailed explanation
  • Factory Method Patterns — separating options and method patterns
查看完整文章获取更多细节:
  • 通用工厂函数签名 —— 深入解析签名规则
  • 停止将客户端作为参数传递 —— 实践指南
  • 工厂函数模式 —— 详细解释
  • 工厂方法模式 —— 配置与方法模式的分离