8. Factory Method
Design Patterns. Урок: Factory Method (Фабричный метод)
Заголовок раздела «Design Patterns. Урок: Factory Method (Фабричный метод)»Factory Method — порождающий паттерн, определяющий интерфейс для создания объекта, но оставляющий подклассам решение о том, какой класс инстанцировать.
Говоря проще: вместо new ConcreteClass() вызываешь factory.create() — и не думаешь о деталях создания.
Проблема: жёсткое создание объектов
Заголовок раздела «Проблема: жёсткое создание объектов»// ❌ Клиентский код знает о всех конкретных типахfunction createPaymentProcessor(type: string): any { if (type === 'stripe') { return new StripeProcessor(process.env.STRIPE_KEY!); } else if (type === 'paypal') { return new PayPalProcessor(process.env.PAYPAL_CLIENT_ID!, process.env.PAYPAL_SECRET!); } else if (type === 'crypto') { return new CryptoProcessor(process.env.CRYPTO_WALLET!); } throw new Error(`Unknown payment type: ${type}`);}Добавление нового типа — правка этой функции. Тестирование — сложно из-за зависимостей.
Factory Method Pattern
Заголовок раздела «Factory Method Pattern»// Абстракция продуктаinterface PaymentProcessor { charge(amount: number, currency: string): Promise<PaymentResult>; refund(transactionId: string, amount: number): Promise<RefundResult>;}
// Конкретные продуктыclass StripeProcessor implements PaymentProcessor { constructor(private apiKey: string) {}
async charge(amount: number, currency: string): Promise<PaymentResult> { // Stripe-специфичная логика return { success: true, transactionId: `str_${Date.now()}` }; }
async refund(transactionId: string, amount: number): Promise<RefundResult> { return { success: true }; }}
class PayPalProcessor implements PaymentProcessor { constructor(private clientId: string, private secret: string) {}
async charge(amount: number, currency: string): Promise<PaymentResult> { // PayPal-специфичная логика return { success: true, transactionId: `pp_${Date.now()}` }; }
async refund(transactionId: string, amount: number): Promise<RefundResult> { return { success: true }; }}
// Абстрактная фабрикаabstract class PaymentProcessorFactory { // Factory Method — переопределяется подклассами abstract createProcessor(): PaymentProcessor;
// Шаблонный метод использует factory method async processPayment(amount: number, currency: string): Promise<PaymentResult> { const processor = this.createProcessor(); // Создаётся нужный тип const result = await processor.charge(amount, currency); await this.logTransaction(result); return result; }
private async logTransaction(result: PaymentResult): Promise<void> { console.log(`Transaction: ${result.transactionId}`); }}
// Конкретные фабрикиclass StripeProcessorFactory extends PaymentProcessorFactory { createProcessor(): PaymentProcessor { return new StripeProcessor(process.env.STRIPE_KEY!); }}
class PayPalProcessorFactory extends PaymentProcessorFactory { createProcessor(): PaymentProcessor { return new PayPalProcessor( process.env.PAYPAL_CLIENT_ID!, process.env.PAYPAL_SECRET!, ); }}
// Использованиеconst factory = new StripeProcessorFactory();const result = await factory.processPayment(100, 'USD');Simple Factory (не GoF, но популярен)
Заголовок раздела «Simple Factory (не GoF, но популярен)»Простая фабрика — статический метод или функция, инкапсулирующая создание:
// Простая фабрика — не паттерн GoF, но широко используетсяclass NotificationFactory { static create(type: 'email' | 'sms' | 'push', config: NotificationConfig): Notification { switch (type) { case 'email': return new EmailNotification(config.email!, config.smtpServer!); case 'sms': return new SmsNotification(config.phone!, config.twilioKey!); case 'push': return new PushNotification(config.deviceToken!, config.firebaseKey!); default: throw new Error(`Unknown notification type: ${type}`); } }}
// Использованиеconst notification = NotificationFactory.create('email', { smtpServer: 'smtp.gmail.com',});Abstract Factory
Заголовок раздела «Abstract Factory»Abstract Factory — расширение, создающее семейства связанных объектов:
// Семейство UI компонентов (Light / Dark theme)interface Button { render(): string; }interface Input { render(): string; }interface Modal { render(): string; }
// Абстрактная фабрикаinterface UIComponentFactory { createButton(): Button; createInput(): Input; createModal(): Modal;}
// Конкретная фабрика для светлой темыclass LightThemeFactory implements UIComponentFactory { createButton(): Button { return new LightButton(); } createInput(): Input { return new LightInput(); } createModal(): Modal { return new LightModal(); }}
// Конкретная фабрика для тёмной темыclass DarkThemeFactory implements UIComponentFactory { createButton(): Button { return new DarkButton(); } createInput(): Input { return new DarkInput(); } createModal(): Modal { return new DarkModal(); }}
// Клиент работает только с абстракциейclass App { constructor(private uiFactory: UIComponentFactory) {}
render(): string { const btn = this.uiFactory.createButton(); const input = this.uiFactory.createInput(); return `${btn.render()} ${input.render()}`; }}
// Переключение темы без изменения Appconst lightApp = new App(new LightThemeFactory());const darkApp = new App(new DarkThemeFactory());Фабрика в React
Заголовок раздела «Фабрика в React»// Фабрика для создания компонентов формtype FieldType = 'text' | 'email' | 'password' | 'select' | 'textarea';
function createFormField(type: FieldType, props: FieldProps): ReactElement { const fieldComponents = { text: TextField, email: EmailField, password: PasswordField, select: SelectField, textarea: TextareaField, };
const Component = fieldComponents[type]; return <Component {...props} />;}
// Использованиеconst fields: Array<{ type: FieldType; name: string; label: string }> = [ { type: 'text', name: 'username', label: 'Username' }, { type: 'email', name: 'email', label: 'Email' }, { type: 'password', name: 'password', label: 'Password' },];
function RegistrationForm() { return ( <form> {fields.map(field => createFormField(field.type, field))} </form> );}Практические задания
Заголовок раздела «Практические задания»-
Создай
LoggerFactoryс реализациямиConsoleLogger,FileLogger,RemoteLogger. Фабрика определяет тип по env переменнойLOG_TARGET. -
Реализуй Abstract Factory для создания адаптеров баз данных:
DatabaseAdapterFactory→PostgresAdapterиSQLiteAdapter. -
Напиши тест для
PaymentProcessorFactoryиспользуя мок-фабрику. -
Чем Factory Method отличается от Simple Factory? Когда использовать каждый?
-
Найди использование паттерна Factory в исходниках React или Next.js.