← Назад к вопросам

К какому паттерну относится шаблонный метод

2.0 Middle🔥 121 комментариев
#Архитектура и паттерны#ООП

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Template Method - паттерн проектирования

Шаблонный метод (Template Method) — это паттерн поведенческого проектирования, который определяет скелет алгоритма в базовом классе, но оставляет отдельные шаги (методы) для реализации в подклассах.

Характеристика паттерна

  • Тип: Behavioral (поведенческий) паттерн
  • Принцип: DRY (Don't Repeat Yourself) — избегаем дублирования кода
  • Назначение: Позволить подклассам переопределить определённые шаги алгоритма, не изменяя структуру самого алгоритма

Структура паттерна Template Method

  1. Абстрактный базовый класс — определяет шаблонный метод и абстрактные методы
  2. Конкретные подклассы — реализуют абстрактные методы
  3. Клиент — работает с экземплярами подклассов

Примеры Template Method в Node.js

Пример 1: Обработка различных форматов данных

abstract class DataProcessor {
  // Шаблонный метод - определяет скелет алгоритма
  final process(data: string): void {
    this.parse(data);
    this.validate();
    this.transform();
    this.save();
  }

  // Абстрактные методы, которые должны быть реализованы подклассами
  abstract parse(data: string): void;
  abstract validate(): void;
  abstract transform(): void;
  abstract save(): void;
}

// Конкретная реализация для CSV
class CSVProcessor extends DataProcessor {
  private data: any;

  parse(data: string): void {
    this.data = data.split('\n').map(line => line.split(','));
    console.log('CSV parsed');
  }

  validate(): void {
    console.log('CSV validation');
  }

  transform(): void {
    console.log('CSV transformation');
  }

  save(): void {
    console.log('CSV saved to database');
  }
}

// Конкретная реализация для JSON
class JSONProcessor extends DataProcessor {
  private data: any;

  parse(data: string): void {
    this.data = JSON.parse(data);
    console.log('JSON parsed');
  }

  validate(): void {
    console.log('JSON schema validation');
  }

  transform(): void {
    console.log('JSON transformation');
  }

  save(): void {
    console.log('JSON saved to database');
  }
}

// Использование
const csvProcessor = new CSVProcessor();
const jsonProcessor = new JSONProcessor();

const csvData = 'name,age\nJohn,30\nJane,25';
const jsonData = '{"name":"John","age":30}';

csvProcessor.process(csvData);  // Выведет все шаги для CSV
jsonProcessor.process(jsonData); // Выведет все шаги для JSON

Пример 2: API запросы с различными авторизациями

abstract class APIClient {
  protected baseURL: string;

  // Шаблонный метод
  async fetch(endpoint: string, method: 'GET' | 'POST' = 'GET', body?: any) {
    const url = this.buildURL(endpoint);
    const headers = this.getHeaders();
    const auth = await this.authenticate();
    
    const response = await this.makeRequest(url, method, { ...headers, ...auth }, body);
    return this.handleResponse(response);
  }

  // Абстрактные методы
  abstract authenticate(): Promise<Record<string, string>>;
  abstract getHeaders(): Record<string, string>;

  // Конкретные методы, переиспользуемые всеми подклассами
  protected buildURL(endpoint: string): string {
    return `${this.baseURL}${endpoint}`;
  }

  protected async makeRequest(
    url: string,
    method: string,
    headers: Record<string, string>,
    body?: any,
  ) {
    return fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined });
  }

  protected handleResponse(response: Response) {
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  }
}

// Bearer Token авторизация
class BearerAPIClient extends APIClient {
  constructor(private token: string) {
    super();
    this.baseURL = 'https://api.example.com';
  }

  async authenticate(): Promise<Record<string, string>> {
    return { Authorization: `Bearer ${this.token}` };
  }

  getHeaders(): Record<string, string> {
    return { 'Content-Type': 'application/json' };
  }
}

// Basic Auth авторизация
class BasicAuthAPIClient extends APIClient {
  constructor(private username: string, private password: string) {
    super();
    this.baseURL = 'https://api.example.com';
  }

  async authenticate(): Promise<Record<string, string>> {
    const credentials = btoa(`${this.username}:${this.password}`);
    return { Authorization: `Basic ${credentials}` };
  }

  getHeaders(): Record<string, string> {
    return { 'Content-Type': 'application/json', 'User-Agent': 'MyApp/1.0' };
  }
}

// API Key авторизация
class APIKeyClient extends APIClient {
  constructor(private apiKey: string) {
    super();
    this.baseURL = 'https://api.example.com';
  }

  async authenticate(): Promise<Record<string, string>> {
    return { 'X-API-Key': this.apiKey };
  }

  getHeaders(): Record<string, string> {
    return { 'Content-Type': 'application/json' };
  }
}

// Использование
const bearerClient = new BearerAPIClient('my-token-123');
const basicClient = new BasicAuthAPIClient('user', 'password');
const apiKeyClient = new APIKeyClient('sk-1234567890');

// Все клиенты используют один и тот же алгоритм fetch()
await bearerClient.fetch('/users');
await basicClient.fetch('/data');
await apiKeyClient.fetch('/products');

Пример 3: Pipeline обработки с NestJS

abstract class BaseService {
  // Шаблонный метод - определяет общий workflow
  async processRequest(data: any) {
    console.log('1. Начало обработки');
    const validated = await this.validate(data);
    const processed = await this.process(validated);
    const result = await this.finalize(processed);
    console.log('4. Обработка завершена');
    return result;
  }

  // Методы, которые могут быть переопределены
  protected async validate(data: any): Promise<any> {
    console.log('2. Валидация (базовая)');
    return data;
  }

  protected async process(data: any): Promise<any> {
    console.log('3. Обработка (базовая)');
    return data;
  }

  protected async finalize(data: any): Promise<any> {
    console.log('4. Финализация (базовая)');
    return data;
  }
}

@Injectable()
class UserService extends BaseService {
  protected async validate(data: any): Promise<any> {
    console.log('2. Валидация email')
    if (!data.email) throw new BadRequestException('Email обязателен');
    return data;
  }

  protected async process(data: any): Promise<any> {
    console.log('3. Хеширование пароля')
    data.password = await bcrypt.hash(data.password, 10);
    return data;
  }

  protected async finalize(data: any): Promise<any> {
    console.log('4. Сохранение в БД')
    return this.repository.save(data);
  }
}

@Injectable()
class ProductService extends BaseService {
  protected async validate(data: any): Promise<any> {
    console.log('2. Проверка цены')
    if (data.price < 0) throw new BadRequestException('Цена не может быть отрицательной');
    return data;
  }

  protected async process(data: any): Promise<any> {
    console.log('3. Расчёт налогов')
    data.taxAmount = data.price * 0.18;
    return data;
  }
}

Преимущества Template Method

  1. DRY принцип — избегаем дублирования общего кода
  2. Инверсия управления — подклассы контролируют конкретные шаги
  3. Прозрачность — алгоритм виден в базовом классе
  4. Масштабируемость — легко добавлять новые реализации
  5. Консистентность — гарантирует правильный порядок шагов

Когда использовать Template Method

  • Когда есть общий алгоритм, но шаги отличаются
  • Когда нужно избежать дублирования кода в подклассах
  • Когда порядок выполнения критичен
  • В обработке данных (парсинг, валидация, трансформация)
  • В API клиентах с разными авторизациями
  • В middleware цепочках

Отличие от Strategy

Template Method:  Алгоритм в базовом классе, подклассы переопределяют шаги
Strategy:         Алгоритмы в отдельных классах, клиент выбирает нужный

Template Method — это поведенческий паттерн, который определяет скелет алгоритма в базовом классе, позволяя подклассам переопределять отдельные шаги без изменения структуры алгоритма.