工厂模式
问题
什么是工厂模式?简单工厂、工厂方法、抽象工厂有什么区别?前端有哪些实际应用?
答案
工厂模式是一种创建型设计模式,将对象的创建逻辑封装起来,通过统一接口创建对象,调用者无需关心具体实现。
核心概念
三种工厂模式对比
| 类型 | 说明 | 适用场景 |
|---|---|---|
| 简单工厂 | 一个工厂创建所有产品 | 产品类型少且固定 |
| 工厂方法 | 每个产品对应一个工厂 | 产品扩展频繁 |
| 抽象工厂 | 创建产品家族 | 多系列产品组合 |
简单工厂模式
基础实现
// 产品接口
interface Button {
render(): string;
onClick(handler: () => void): void;
}
// 具体产品
class PrimaryButton implements Button {
render() {
return '<button class="primary">Primary</button>';
}
onClick(handler: () => void) {
console.log('Primary button clicked');
handler();
}
}
class DangerButton implements Button {
render() {
return '<button class="danger">Danger</button>';
}
onClick(handler: () => void) {
console.log('Danger button clicked');
handler();
}
}
class LinkButton implements Button {
render() {
return '<button class="link">Link</button>';
}
onClick(handler: () => void) {
console.log('Link button clicked');
handler();
}
}
// 简单工厂
type ButtonType = 'primary' | 'danger' | 'link';
class ButtonFactory {
static create(type: ButtonType): Button {
switch (type) {
case 'primary':
return new PrimaryButton();
case 'danger':
return new DangerButton();
case 'link':
return new LinkButton();
default:
throw new Error(`Unknown button type: ${type}`);
}
}
}
// 使用
const primaryBtn = ButtonFactory.create('primary');
const dangerBtn = ButtonFactory.create('danger');
console.log(primaryBtn.render());
使用 Map 优化
// 避免 switch-case,使用 Map
const buttonMap = new Map<ButtonType, new () => Button>([
['primary', PrimaryButton],
['danger', DangerButton],
['link', LinkButton],
]);
class ButtonFactoryV2 {
static create(type: ButtonType): Button {
const ButtonClass = buttonMap.get(type);
if (!ButtonClass) {
throw new Error(`Unknown button type: ${type}`);
}
return new ButtonClass();
}
// 支持注册新类型
static register(type: ButtonType, ButtonClass: new () => Button) {
buttonMap.set(type, ButtonClass);
}
}
工厂方法模式
// 产品接口
interface Dialog {
title: string;
render(): string;
}
// 工厂接口
interface DialogFactory {
createDialog(): Dialog;
}
// 具体产品
class ConfirmDialog implements Dialog {
title = '确认';
render() {
return `
<div class="dialog confirm">
<h2>${this.title}</h2>
<button>确认</button>
<button>取消</button>
</div>
`;
}
}
class AlertDialog implements Dialog {
title = '警告';
render() {
return `
<div class="dialog alert">
<h2>${this.title}</h2>
<button>知道了</button>
</div>
`;
}
}
// 具体工厂
class ConfirmDialogFactory implements DialogFactory {
createDialog() {
return new ConfirmDialog();
}
}
class AlertDialogFactory implements DialogFactory {
createDialog() {
return new AlertDialog();
}
}
// 使用
function showDialog(factory: DialogFactory) {
const dialog = factory.createDialog();
console.log(dialog.render());
}
showDialog(new ConfirmDialogFactory());
showDialog(new AlertDialogFactory());
抽象工厂模式
// 产品接口
interface Input {
render(): string;
getValue(): string;
}
interface Select {
render(): string;
getSelected(): string;
}
interface Checkbox {
render(): string;
isChecked(): boolean;
}
// 抽象工厂
interface UIFactory {
createInput(): Input;
createSelect(): Select;
createCheckbox(): Checkbox;
}
// Ant Design 风格产品
class AntInput implements Input {
render() {
return '<input class="ant-input" />';
}
getValue() {
return 'ant-input-value';
}
}
class AntSelect implements Select {
render() {
return '<select class="ant-select"></select>';
}
getSelected() {
return 'ant-selected';
}
}
class AntCheckbox implements Checkbox {
render() {
return '<input type="checkbox" class="ant-checkbox" />';
}
isChecked() {
return false;
}
}
// Element UI 风格产品
class ElementInput implements Input {
render() {
return '<input class="el-input" />';
}
getValue() {
return 'el-input-value';
}
}
class ElementSelect implements Select {
render() {
return '<select class="el-select"></select>';
}
getSelected() {
return 'el-selected';
}
}
class ElementCheckbox implements Checkbox {
render() {
return '<input type="checkbox" class="el-checkbox" />';
}
isChecked() {
return false;
}
}
// 具体工厂
class AntDesignFactory implements UIFactory {
createInput() {
return new AntInput();
}
createSelect() {
return new AntSelect();
}
createCheckbox() {
return new AntCheckbox();
}
}
class ElementUIFactory implements UIFactory {
createInput() {
return new ElementInput();
}
createSelect() {
return new ElementSelect();
}
createCheckbox() {
return new ElementCheckbox();
}
}
// 使用 - 切换 UI 框架只需更换工厂
function renderForm(factory: UIFactory) {
const input = factory.createInput();
const select = factory.createSelect();
const checkbox = factory.createCheckbox();
return `
<form>
${input.render()}
${select.render()}
${checkbox.render()}
</form>
`;
}
// 使用 Ant Design
console.log(renderForm(new AntDesignFactory()));
// 使用 Element UI
console.log(renderForm(new ElementUIFactory()));
前端实际应用
1. 组件工厂
import React, { ComponentType } from 'react';
interface FieldProps {
name: string;
label: string;
value: unknown;
onChange: (value: unknown) => void;
}
// 表单字段组件
const TextField: React.FC<FieldProps> = ({ name, label, value, onChange }) => (
<div>
<label>{label}</label>
<input
name={name}
value={value as string}
onChange={(e) => onChange(e.target.value)}
/>
</div>
);
const SelectField: React.FC<FieldProps & { options: string[] }> = ({
name,
label,
value,
onChange,
options,
}) => (
<div>
<label>{label}</label>
<select
name={name}
value={value as string}
onChange={(e) => onChange(e.target.value)}
>
{options.map((opt) => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
</div>
);
const CheckboxField: React.FC<FieldProps> = ({ name, label, value, onChange }) => (
<div>
<label>
<input
type="checkbox"
name={name}
checked={value as boolean}
onChange={(e) => onChange(e.target.checked)}
/>
{label}
</label>
</div>
);
// 字段工厂
type FieldType = 'text' | 'select' | 'checkbox';
const fieldComponents: Record<FieldType, ComponentType<FieldProps & Record<string, unknown>>> = {
text: TextField,
select: SelectField,
checkbox: CheckboxField,
};
function createField(type: FieldType, props: FieldProps & Record<string, unknown>) {
const Component = fieldComponents[type];
if (!Component) {
throw new Error(`Unknown field type: ${type}`);
}
return <Component {...props} />;
}
// 使用
function DynamicForm() {
const fields = [
{ type: 'text' as const, name: 'username', label: '用户名' },
{ type: 'select' as const, name: 'role', label: '角色', options: ['admin', 'user'] },
{ type: 'checkbox' as const, name: 'remember', label: '记住我' },
];
return (
<form>
{fields.map((field) =>
createField(field.type, {
...field,
value: '',
onChange: (v) => console.log(v),
})
)}
</form>
);
}
2. 图表工厂
interface Chart {
data: unknown[];
render(container: HTMLElement): void;
update(data: unknown[]): void;
destroy(): void;
}
class LineChart implements Chart {
data: unknown[] = [];
render(container: HTMLElement) {
console.log('Rendering line chart in', container);
}
update(data: unknown[]) {
this.data = data;
console.log('Updating line chart');
}
destroy() {
console.log('Destroying line chart');
}
}
class BarChart implements Chart {
data: unknown[] = [];
render(container: HTMLElement) {
console.log('Rendering bar chart in', container);
}
update(data: unknown[]) {
this.data = data;
console.log('Updating bar chart');
}
destroy() {
console.log('Destroying bar chart');
}
}
class PieChart implements Chart {
data: unknown[] = [];
render(container: HTMLElement) {
console.log('Rendering pie chart in', container);
}
update(data: unknown[]) {
this.data = data;
console.log('Updating pie chart');
}
destroy() {
console.log('Destroying pie chart');
}
}
// 图表工厂
type ChartType = 'line' | 'bar' | 'pie';
class ChartFactory {
private static chartMap = new Map<ChartType, new () => Chart>([
['line', LineChart],
['bar', BarChart],
['pie', PieChart],
]);
static create(type: ChartType): Chart {
const ChartClass = this.chartMap.get(type);
if (!ChartClass) {
throw new Error(`Unknown chart type: ${type}`);
}
return new ChartClass();
}
static register(type: ChartType, ChartClass: new () => Chart) {
this.chartMap.set(type, ChartClass);
}
}
// 使用
const chart = ChartFactory.create('line');
chart.render(document.getElementById('chart')!);
3. HTTP 请求适配器工厂
interface HttpAdapter {
get<T>(url: string): Promise<T>;
post<T>(url: string, data: unknown): Promise<T>;
}
class FetchAdapter implements HttpAdapter {
async get<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json();
}
async post<T>(url: string, data: unknown): Promise<T> {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
return response.json();
}
}
class AxiosAdapter implements HttpAdapter {
async get<T>(url: string): Promise<T> {
// 模拟 axios
const response = await fetch(url);
return response.json();
}
async post<T>(url: string, data: unknown): Promise<T> {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
return response.json();
}
}
// 工厂
type AdapterType = 'fetch' | 'axios';
class HttpAdapterFactory {
static create(type: AdapterType): HttpAdapter {
switch (type) {
case 'fetch':
return new FetchAdapter();
case 'axios':
return new AxiosAdapter();
default:
return new FetchAdapter();
}
}
}
// 使用
const http = HttpAdapterFactory.create('fetch');
const data = await http.get('/api/users');
4. 消息通知工厂
interface Notification {
show(message: string): void;
hide(): void;
}
class ToastNotification implements Notification {
private element: HTMLDivElement | null = null;
show(message: string) {
this.element = document.createElement('div');
this.element.className = 'toast';
this.element.textContent = message;
document.body.appendChild(this.element);
setTimeout(() => this.hide(), 3000);
}
hide() {
this.element?.remove();
}
}
class ModalNotification implements Notification {
private element: HTMLDivElement | null = null;
show(message: string) {
this.element = document.createElement('div');
this.element.className = 'modal-notification';
this.element.innerHTML = `
<div class="modal-content">
<p>${message}</p>
<button onclick="this.parentElement.parentElement.remove()">关闭</button>
</div>
`;
document.body.appendChild(this.element);
}
hide() {
this.element?.remove();
}
}
class BannerNotification implements Notification {
private element: HTMLDivElement | null = null;
show(message: string) {
this.element = document.createElement('div');
this.element.className = 'banner';
this.element.textContent = message;
document.body.prepend(this.element);
}
hide() {
this.element?.remove();
}
}
// 工厂
type NotificationType = 'toast' | 'modal' | 'banner';
function createNotification(type: NotificationType): Notification {
const notifications: Record<NotificationType, new () => Notification> = {
toast: ToastNotification,
modal: ModalNotification,
banner: BannerNotification,
};
return new notifications[type]();
}
// 使用
const notification = createNotification('toast');
notification.show('操作成功!');
常见面试问题
Q1: 简单工厂、工厂方法、抽象工厂的区别?
答案:
// 1. 简单工厂 - 一个工厂创建所有产品
class SimpleFactory {
static create(type: string) {
if (type === 'A') return new ProductA();
if (type === 'B') return new ProductB();
}
}
// 2. 工厂方法 - 每个产品对应工厂
interface Factory {
create(): Product;
}
class ProductAFactory implements Factory {
create() {
return new ProductA();
}
}
// 3. 抽象工厂 - 创建产品家族
interface UIFactory {
createButton(): Button;
createInput(): Input;
}
class MacUIFactory implements UIFactory {
createButton() {
return new MacButton();
}
createInput() {
return new MacInput();
}
}
| 对比项 | 简单工厂 | 工厂方法 | 抽象工厂 |
|---|---|---|---|
| 复杂度 | 低 | 中 | 高 |
| 扩展性 | 需修改工厂 | 新增工厂类 | 新增工厂类 |
| 产品关系 | 单一产品 | 单一产品 | 产品家族 |
| 适用场景 | 产品类型少 | 产品扩展多 | 多系列产品 |
Q2: 工厂模式的优缺点?
答案:
| 优点 | 缺点 |
|---|---|
| 解耦创建和使用 | 增加类的数量 |
| 易扩展新产品 | 简单工厂违反开闭原则 |
| 符合单一职责 | 增加系统抽象性 |
| 隐藏实现细节 | - |
Q3: 什么时候使用工厂模式?
答案:
// 场景 1:对象创建逻辑复杂
class ComplexObject {
constructor() {
// 需要大量初始化
}
}
const factory = {
create: () => {
const obj = new ComplexObject();
// 复杂初始化...
return obj;
},
};
// 场景 2:根据配置创建不同对象
const componentFactory = (config: { type: string }) => {
switch (config.type) {
case 'button':
return new Button();
case 'input':
return new Input();
}
};
// 场景 3:需要统一管理对象创建(如池化、缓存)
class ConnectionFactory {
private pool: Connection[] = [];
create() {
if (this.pool.length > 0) {
return this.pool.pop()!;
}
return new Connection();
}
release(conn: Connection) {
this.pool.push(conn);
}
}
Q4: React 中的工厂模式应用?
答案:
// React.createElement 就是工厂函数
const element = React.createElement('div', { className: 'box' }, 'Hello');
// 高阶组件也是工厂模式
function withLoading<P extends object>(Component: React.ComponentType<P>) {
return function WithLoadingComponent(props: P & { loading: boolean }) {
const { loading, ...rest } = props;
if (loading) return <div>Loading...</div>;
return <Component {...(rest as P)} />;
};
}
// 动态组件渲染
const componentMap = {
text: TextInput,
select: SelectInput,
checkbox: CheckboxInput,
};
function renderField(type: keyof typeof componentMap, props: FieldProps) {
const Component = componentMap[type];
return <Component {...props} />;
}