创建型设计模式关注对象的创建过程,帮助我们将对象的创建和使用分离,使系统更加灵活和可扩展。本章将介绍JavaScript中常用的创建型设计模式。
设计模式是软件开发中经过验证的解决方案,它们不是代码,而是解决特定问题的模板和思路。
const patternCategories = {
creational: {
focus: '对象的创建过程',
patterns: ['单例模式', '工厂模式', '建造者模式', '原型模式']
},
structural: {
focus: '对象之间的组合关系',
patterns: ['适配器模式', '装饰器模式', '代理模式', '外观模式']
},
behavioral: {
focus: '对象之间的通信',
patterns: ['观察者模式', '策略模式', '命令模式', '迭代器模式']
}
};
const solidPrinciples = {
S: {
name: '单一职责原则',
description: '一个类只负责一项职责',
example: '用户类只管理用户信息,不处理数据库操作'
},
O: {
name: '开放封闭原则',
description: '对扩展开放,对修改封闭',
example: '通过继承扩展功能,而不是修改现有代码'
},
L: {
name: '里氏替换原则',
description: '子类可以替换父类',
example: '所有子类都能完成父类的行为'
},
I: {
name: '接口隔离原则',
description: '不应强迫依赖不使用的接口',
example: '将大接口拆分为多个小接口'
},
D: {
name: '依赖倒置原则',
description: '依赖抽象而非具体实现',
example: '通过接口注入依赖'
}
};
单例模式确保一个类只有一个实例,并提供全局访问点。
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
this.data = {};
}
setData(key, value) {
this.data[key] = value;
}
getData(key) {
return this.data[key];
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2);
instance1.setData('name', '东巴文');
console.log(instance2.getData('name'));
const Singleton = (function() {
let instance;
function createInstance() {
return {
data: {},
setData(key, value) {
this.data[key] = value;
},
getData(key) {
return this.data[key];
}
};
}
return {
getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2);
class Singleton {
static #instance;
constructor() {
if (Singleton.#instance) {
return Singleton.#instance;
}
Singleton.#instance = this;
}
static getInstance() {
if (!Singleton.#instance) {
Singleton.#instance = new Singleton();
}
return Singleton.#instance;
}
}
const singleton = new Proxy(Singleton, {
construct(target, args) {
return target.getInstance();
}
});
const config = {
_instance: null,
init(options) {
if (!this._instance) {
this._instance = {
settings: { ...options },
get(key) {
return this.settings[key];
},
set(key, value) {
this.settings[key] = value;
}
};
}
return this._instance;
}
};
class DatabaseConnection {
static #instance = null;
#connection = null;
constructor(config) {
if (DatabaseConnection.#instance) {
return DatabaseConnection.#instance;
}
this.config = config;
this.connect();
DatabaseConnection.#instance = this;
}
connect() {
console.log(`连接数据库: ${this.config.host}`);
this.#connection = { status: 'connected' };
}
query(sql) {
if (!this.#connection) {
throw new Error('数据库未连接');
}
console.log(`执行查询: ${sql}`);
return { results: [] };
}
static getInstance(config) {
if (!DatabaseConnection.#instance) {
DatabaseConnection.#instance = new DatabaseConnection(config);
}
return DatabaseConnection.#instance;
}
}
class Logger {
static #instance;
#logs = [];
constructor() {
if (Logger.#instance) {
return Logger.#instance;
}
Logger.#instance = this;
}
log(level, message) {
const entry = {
timestamp: new Date().toISOString(),
level,
message
};
this.#logs.push(entry);
console.log(`[${entry.level}] ${entry.message}`);
}
info(message) { this.log('INFO', message); }
warn(message) { this.log('WARN', message); }
error(message) { this.log('ERROR', message); }
getLogs() { return [...this.#logs]; }
}
class Button {
constructor(text) {
this.text = text;
}
render() {
console.log(`渲染按钮: ${this.text}`);
}
}
class Input {
constructor(placeholder) {
this.placeholder = placeholder;
}
render() {
console.log(`渲染输入框: ${this.placeholder}`);
}
}
class Select {
constructor(options) {
this.options = options;
}
render() {
console.log(`渲染下拉框: ${this.options.join(', ')}`);
}
}
function createComponent(type, config) {
switch (type) {
case 'button':
return new Button(config.text);
case 'input':
return new Input(config.placeholder);
case 'select':
return new Select(config.options);
default:
throw new Error(`未知组件类型: ${type}`);
}
}
const button = createComponent('button', { text: '提交' });
const input = createComponent('input', { placeholder: '请输入' });
const select = createComponent('select', { options: ['A', 'B', 'C'] });
class Document {
constructor(title) {
this.title = title;
}
createPage() {
throw new Error('子类必须实现 createPage 方法');
}
}
class TextDocument extends Document {
createPage() {
return new TextPage(this.title);
}
}
class SpreadsheetDocument extends Document {
createPage() {
return new SpreadsheetPage(this.title);
}
}
class TextPage {
constructor(title) {
this.type = 'text';
this.title = title;
this.content = '';
}
}
class SpreadsheetPage {
constructor(title) {
this.type = 'spreadsheet';
this.title = title;
this.cells = [];
}
}
function createDocument(type, title) {
const documentClasses = {
text: TextDocument,
spreadsheet: SpreadsheetDocument
};
const DocumentClass = documentClasses[type];
if (!DocumentClass) {
throw new Error(`未知文档类型: ${type}`);
}
return new DocumentClass(title);
}
class UIFactory {
createButton() {
throw new Error('必须实现 createButton');
}
createInput() {
throw new Error('必须实现 createInput');
}
}
class LightUIFactory extends UIFactory {
createButton() {
return new LightButton();
}
createInput() {
return new LightInput();
}
}
class DarkUIFactory extends UIFactory {
createButton() {
return new DarkButton();
}
createInput() {
return new DarkInput();
}
}
class LightButton {
render() {
return '<button class="light-btn">浅色按钮</button>';
}
}
class DarkButton {
render() {
return '<button class="dark-btn">深色按钮</button>';
}
}
class LightInput {
render() {
return '<input class="light-input" />';
}
}
class DarkInput {
render() {
return '<input class="dark-input" />';
}
}
function renderUI(theme) {
const factory = theme === 'dark' ? new DarkUIFactory() : new LightUIFactory();
const button = factory.createButton();
const input = factory.createInput();
console.log(button.render());
console.log(input.render());
}
class DynamicFactory {
constructor() {
this.registry = new Map();
}
register(type, constructor) {
this.registry.set(type, constructor);
return this;
}
create(type, ...args) {
const Constructor = this.registry.get(type);
if (!Constructor) {
throw new Error(`未注册的类型: ${type}`);
}
return new Constructor(...args);
}
has(type) {
return this.registry.has(type);
}
remove(type) {
this.registry.delete(type);
}
}
const factory = new DynamicFactory();
factory
.register('user', class {
constructor(name, email) {
this.name = name;
this.email = email;
}
})
.register('product', class {
constructor(name, price) {
this.name = name;
this.price = price;
}
});
const user = factory.create('user', '张三', 'zhangsan@example.com');
const product = factory.create('product', '商品A', 99.9);
建造者模式将复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
class Pizza {
constructor() {
this.size = null;
this.crust = null;
this.sauce = null;
this.toppings = [];
this.cheese = null;
}
describe() {
return `${this.size}寸 ${this.crust}底 ${this.sauce}酱 ` +
`${this.toppings.join('、')} ${this.cheese ? '加芝士' : '不加芝士'}`;
}
}
class PizzaBuilder {
constructor() {
this.pizza = new Pizza();
}
setSize(size) {
this.pizza.size = size;
return this;
}
setCrust(crust) {
this.pizza.crust = crust;
return this;
}
setSauce(sauce) {
this.pizza.sauce = sauce;
return this;
}
addTopping(topping) {
this.pizza.toppings.push(topping);
return this;
}
addCheese() {
this.pizza.cheese = true;
return this;
}
build() {
if (!this.pizza.size) throw new Error('必须指定尺寸');
if (!this.pizza.crust) throw new Error('必须指定饼底');
return this.pizza;
}
}
const pizza = new PizzaBuilder()
.setSize(12)
.setCrust('薄底')
.setSauce('番茄')
.addTopping('蘑菇')
.addTopping('青椒')
.addCheese()
.build();
console.log(pizza.describe());
class HttpRequestBuilder {
constructor() {
this.config = {
url: '',
method: 'GET',
headers: {},
params: {},
body: null,
timeout: 5000
};
}
url(url) {
this.config.url = url;
return this;
}
method(method) {
this.config.method = method.toUpperCase();
return this;
}
header(key, value) {
this.config.headers[key] = value;
return this;
}
params(params) {
Object.assign(this.config.params, params);
return this;
}
body(body) {
this.config.body = body;
return this;
}
timeout(ms) {
this.config.timeout = ms;
return this;
}
async send() {
const { url, method, headers, params, body, timeout } = this.config;
const queryString = new URLSearchParams(params).toString();
const fullUrl = queryString ? `${url}?${queryString}` : url;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(fullUrl, {
method,
headers,
body: body ? JSON.stringify(body) : null,
signal: controller.signal
});
return response.json();
} finally {
clearTimeout(timeoutId);
}
}
}
const response = await new HttpRequestBuilder()
.url('/api/users')
.method('POST')
.header('Content-Type', 'application/json')
.header('Authorization', 'Bearer token')
.body({ name: '张三', email: 'zhangsan@example.com' })
.timeout(3000)
.send();
class QueryBuilder {
constructor() {
this.table = '';
this.fields = ['*'];
this.conditions = [];
this.orderByClause = '';
this.limitValue = null;
this.offsetValue = null;
}
select(...fields) {
this.fields = fields.length > 0 ? fields : ['*'];
return this;
}
from(table) {
this.table = table;
return this;
}
where(condition, ...params) {
this.conditions.push({ condition, params });
return this;
}
whereIn(field, values) {
const placeholders = values.map(() => '?').join(', ');
this.conditions.push({
condition: `${field} IN (${placeholders})`,
params: values
});
return this;
}
orderBy(field, direction = 'ASC') {
this.orderByClause = `ORDER BY ${field} ${direction}`;
return this;
}
limit(limit) {
this.limitValue = limit;
return this;
}
offset(offset) {
this.offsetValue = offset;
return this;
}
build() {
if (!this.table) {
throw new Error('必须指定表名');
}
let sql = `SELECT ${this.fields.join(', ')} FROM ${this.table}`;
const params = [];
if (this.conditions.length > 0) {
const whereClauses = this.conditions.map(c => c.condition);
sql += ` WHERE ${whereClauses.join(' AND ')}`;
this.conditions.forEach(c => params.push(...c.params));
}
if (this.orderByClause) {
sql += ` ${this.orderByClause}`;
}
if (this.limitValue !== null) {
sql += ` LIMIT ?`;
params.push(this.limitValue);
}
if (this.offsetValue !== null) {
sql += ` OFFSET ?`;
params.push(this.offsetValue);
}
return { sql, params };
}
}
const query = new QueryBuilder()
.select('id', 'name', 'email')
.from('users')
.where('status = ?', 'active')
.whereIn('role', ['admin', 'editor'])
.orderBy('created_at', 'DESC')
.limit(10)
.offset(20)
.build();
console.log(query.sql);
console.log(query.params);
原型模式通过复制现有对象来创建新对象,而不是从头创建。
const prototype = {
name: '默认名称',
category: '默认分类',
tags: [],
clone() {
const cloned = Object.create(this);
cloned.tags = [...this.tags];
return cloned;
}
};
const product1 = prototype.clone();
product1.name = '产品A';
product1.tags.push('热销');
const product2 = prototype.clone();
product2.name = '产品B';
console.log(product1.tags);
console.log(product2.tags);
class Shape {
constructor(type, color, position) {
this.type = type;
this.color = color;
this.position = { ...position };
}
clone() {
return new Shape(this.type, this.color, this.position);
}
draw() {
console.log(`绘制${this.color}色的${this.type}在位置(${this.position.x}, ${this.position.y})`);
}
}
const originalShape = new Shape('圆形', '红色', { x: 100, y: 100 });
const clonedShape = originalShape.clone();
clonedShape.color = '蓝色';
clonedShape.position.x = 200;
originalShape.draw();
clonedShape.draw();
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (hash.has(obj)) {
return hash.get(obj);
}
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
if (obj instanceof Map) {
const cloned = new Map();
hash.set(obj, cloned);
obj.forEach((value, key) => {
cloned.set(deepClone(key, hash), deepClone(value, hash));
});
return cloned;
}
if (obj instanceof Set) {
const cloned = new Set();
hash.set(obj, cloned);
obj.forEach(value => {
cloned.add(deepClone(value, hash));
});
return cloned;
}
if (Array.isArray(obj)) {
const cloned = [];
hash.set(obj, cloned);
for (let i = 0; i < obj.length; i++) {
cloned[i] = deepClone(obj[i], hash);
}
return cloned;
}
const cloned = Object.create(Object.getPrototypeOf(obj));
hash.set(obj, cloned);
for (const key of Object.keys(obj)) {
cloned[key] = deepClone(obj[key], hash);
}
for (const symbol of Object.getOwnPropertySymbols(obj)) {
cloned[symbol] = deepClone(obj[symbol], hash);
}
return cloned;
}
class PrototypeRegistry {
constructor() {
this.prototypes = new Map();
}
register(key, prototype) {
this.prototypes.set(key, prototype);
}
clone(key) {
const prototype = this.prototypes.get(key);
if (!prototype) {
throw new Error(`未找到原型: ${key}`);
}
return deepClone(prototype);
}
}
class EmailTemplate {
constructor(config) {
this.subject = config.subject;
this.from = config.from;
this.to = [];
this.body = config.body;
this.variables = {};
}
setVariable(key, value) {
this.variables[key] = value;
return this;
}
addTo(email) {
this.to.push(email);
return this;
}
render() {
let body = this.body;
for (const [key, value] of Object.entries(this.variables)) {
body = body.replace(new RegExp(`{{${key}}}`, 'g'), value);
}
return body;
}
clone() {
const cloned = new EmailTemplate({
subject: this.subject,
from: this.from,
body: this.body
});
cloned.to = [...this.to];
cloned.variables = { ...this.variables };
return cloned;
}
}
const welcomeTemplate = new EmailTemplate({
subject: '欢迎加入',
from: 'noreply@example.com',
body: '亲爱的{{name}},欢迎加入我们的社区!'
});
const email1 = welcomeTemplate.clone()
.addTo('user1@example.com')
.setVariable('name', '张三');
const email2 = welcomeTemplate.clone()
.addTo('user2@example.com')
.setVariable('name', '李四');
console.log(email1.render());
console.log(email2.render());
class ObjectPool {
constructor(factory, options = {}) {
this.factory = factory;
this.maxSize = options.maxSize || 100;
this.initialSize = options.initialSize || 10;
this.pool = [];
this.active = new Set();
this.initialize();
}
initialize() {
for (let i = 0; i < this.initialSize; i++) {
this.pool.push(this.factory());
}
}
acquire(...args) {
let obj;
if (this.pool.length > 0) {
obj = this.pool.pop();
} else if (this.active.size < this.maxSize) {
obj = this.factory();
} else {
throw new Error('对象池已满');
}
if (obj.init) {
obj.init(...args);
}
this.active.add(obj);
return obj;
}
release(obj) {
if (!this.active.has(obj)) return;
this.active.delete(obj);
if (obj.reset) {
obj.reset();
}
this.pool.push(obj);
}
get size() {
return this.pool.length + this.active.size;
}
get available() {
return this.pool.length;
}
clear() {
this.pool = [];
this.active.clear();
}
}
class Bullet {
constructor() {
this.reset();
}
init(x, y, velocity) {
this.x = x;
this.y = y;
this.velocity = { ...velocity };
this.active = true;
}
reset() {
this.x = 0;
this.y = 0;
this.velocity = { x: 0, y: 0 };
this.active = false;
}
update() {
this.x += this.velocity.x;
this.y += this.velocity.y;
}
}
const bulletPool = new ObjectPool(() => new Bullet(), {
initialSize: 50,
maxSize: 200
});
🏭 工厂模式选择指南
- 简单工厂:创建逻辑简单,类型较少
- 工厂方法:需要扩展新的产品类型
- 抽象工厂:需要创建产品族(相关产品组合)
📦 单例模式注意事项
单例模式虽然方便,但也带来一些问题:
- 全局状态可能导致代码难以测试
- 隐藏的依赖关系
- 多线程环境下的竞态条件(JavaScript中较少见)
考虑使用依赖注入替代单例
下一章将探讨 结构型模式,学习如何组合类和对象以形成更大的结构。