Приведи пример использования Interface Segregation Principle
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Interface Segregation Principle (ISP) — практический пример
Interface Segregation Principle гласит: "Клиенты не должны зависеть от интерфейсов, которые они не используют". Вместо одного большого интерфейса лучше создать несколько маленьких, специализированных интерфейсов.
Проблема: монолитный интерфейс
Представим систему платежей для e-commerce приложения.
// ❌ Плохо — один большой интерфейс для всех платёжных методов
interface PaymentProcessor {
processPayment(amount: number): Promise<void>;
refund(amount: number): Promise<void>;
getBalance(): Promise<number>;
setupRecurringPayment(interval: string): Promise<void>;
validateCardDetails(card: CardInfo): boolean;
authenticate2FA(code: string): Promise<void>;
getTransactionHistory(): Promise<Transaction[]>;
}
// Когда мы реализуем этот интерфейс для разных методов платежа,
// появляются проблемы:
class BitcoinPayment implements PaymentProcessor {
processPayment(amount: number): Promise<void> {
// Криптовалюта может обработать платёж
}
refund(amount: number): Promise<void> {
// Но возврат сложнее — обычно нет возврата в крипто
throw new Error('Bitcoin does not support refunds');
}
getBalance(): Promise<number> {
// Баланс? Крипто кошелька или что?
}
setupRecurringPayment(interval: string): Promise<void> {
// Крипто не поддерживает автоматические платежи
throw new Error('Not supported');
}
validateCardDetails(card: CardInfo): boolean {
// У крипто нет карточек!
throw new Error('Not applicable');
}
authenticate2FA(code: string): Promise<void> {
// Может и не нужна
throw new Error('Not implemented');
}
getTransactionHistory(): Promise<Transaction[]> {
// Может быть
}
}
Проблемы этого подхода:
- Классы вынуждены реализовывать ненужные методы
- Нарушается принцип единственной ответственности
- Клиентский код должен знать обо всех методах, даже если он их не использует
- Сложнее тестировать и поддерживать
Решение: разделить интерфейсы
// ✅ Хорошо — специализированные интерфейсы
// Базовый интерфейс — все платежи должны это делать
interface BasicPayment {
processPayment(amount: number): Promise<void>;
}
// Опциональные возможности
interface Refundable {
refund(amount: number): Promise<void>;
}
interface BalanceProvider {
getBalance(): Promise<number>;
}
interface RecurringPaymentSupport {
setupRecurringPayment(interval: string): Promise<void>;
cancelRecurringPayment(): Promise<void>;
}
interface CardValidation {
validateCardDetails(card: CardInfo): boolean;
}
interface TwoFactorAuth {
authenticate2FA(code: string): Promise<void>;
}
interface TransactionHistory {
getTransactionHistory(): Promise<Transaction[]>;
}
// Теперь каждый платёжный метод реализует только нужные ему интерфейсы
class CreditCardPayment implements
BasicPayment,
Refundable,
BalanceProvider,
RecurringPaymentSupport,
CardValidation,
TwoFactorAuth,
TransactionHistory {
async processPayment(amount: number): Promise<void> {
console.log(`Processing credit card payment: $${amount}`);
// логика обработки
}
async refund(amount: number): Promise<void> {
console.log(`Refunding credit card payment: $${amount}`);
}
async getBalance(): Promise<number> {
return 5000; // кредитный лимит
}
async setupRecurringPayment(interval: string): Promise<void> {
console.log(`Setting up recurring payment every ${interval}`);
}
cancelRecurringPayment(): Promise<void> {
console.log('Cancelling recurring payment');
return Promise.resolve();
}
validateCardDetails(card: CardInfo): boolean {
return card.cvv && card.expiryDate && card.cardNumber;
}
async authenticate2FA(code: string): Promise<void> {
console.log(`Authenticating with 2FA code: ${code}`);
}
async getTransactionHistory(): Promise<Transaction[]> {
return [];
}
}
class BitcoinPayment implements BasicPayment, BalanceProvider, TransactionHistory {
async processPayment(amount: number): Promise<void> {
console.log(`Processing bitcoin payment: ${amount} BTC`);
}
async getBalance(): Promise<number> {
return 0.5; // баланс в BTC
}
async getTransactionHistory(): Promise<Transaction[]> {
return []; // данные из блокчейна
}
// Остальные методы НЕ реализуем — они не нужны для крипто
}
class ApplePayment implements BasicPayment, Refundable, TwoFactorAuth {
async processPayment(amount: number): Promise<void> {
console.log(`Processing Apple Pay: $${amount}`);
}
async refund(amount: number): Promise<void> {
console.log(`Refunding Apple Pay: $${amount}`);
}
async authenticate2FA(code: string): Promise<void> {
console.log('Biometric authentication through Apple Pay');
}
}
Использование в клиентском коде
// Теперь функции получают только нужные интерфейсы
// Функция, которая обрабатывает платёжи
function processUserPayment(processor: BasicPayment, amount: number): void {
processor.processPayment(amount);
// Она не знает о других методах и не зависит от них
}
// Функция, которая делает возвраты
function processRefund(processor: Refundable, amount: number): void {
processor.refund(amount);
// Можно передать только платежи, которые поддерживают возврат
}
// Функция для двухфакторной аутентификации
function authenticate(processor: TwoFactorAuth, code: string): void {
processor.authenticate2FA(code);
// Работает только с платежами, которые это поддерживают
}
// Использование
const creditCard = new CreditCardPayment();
const bitcoin = new BitcoinPayment();
processUserPayment(creditCard, 100); // OK
processUserPayment(bitcoin, 0.005); // OK
processRefund(creditCard, 50); // OK
processRefund(bitcoin, 0.002); // ❌ Ошибка типа — Bitcoin не поддерживает refund!
authenticate(creditCard, '123456'); // OK
authenticate(bitcoin, '123456'); // ❌ Ошибка типа — Bitcoin не имеет 2FA!
Реальный пример в NestJS
// services/payment.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class PaymentService {
// Сервис только обрабатывает платежи
async processPayment(processor: BasicPayment, amount: number): Promise<void> {
await processor.processPayment(amount);
}
}
// services/refund.service.ts
@Injectable()
export class RefundService {
// Сервис возвратов работает только с поддерживающими возврат
async processRefund(processor: Refundable, amount: number): Promise<void> {
await processor.refund(amount);
}
}
// services/recurring-payment.service.ts
@Injectable()
export class RecurringPaymentService {
async setupRecurring(processor: RecurringPaymentSupport, interval: string): Promise<void> {
await processor.setupRecurringPayment(interval);
}
}
// controller
@Controller('payments')
export class PaymentController {
constructor(
private paymentService: PaymentService,
private refundService: RefundService,
private recurringService: RecurringPaymentService,
) {}
@Post('process')
async process(@Body() { processor, amount }: ProcessPaymentDto) {
// Каждый сервис работает со своим специализированным интерфейсом
return this.paymentService.processPayment(processor, amount);
}
@Post('refund')
async refund(@Body() { processor, amount }: RefundDto) {
return this.refundService.processRefund(processor, amount);
}
}
Преимущества ISP
✅ Гибкость — классы реализуют только нужный функционал ✅ Меньше связанности — клиентский код не зависит от ненужных методов ✅ Проще тестировать — можем создавать моки только нужных интерфейсов ✅ Проще расширять — добавление нового платёжного метода не требует реализации всех методов ✅ Понятнее типизация — TypeScript покажет ошибку, если метод не поддерживается
Итоги
Interface Segregation Principle — это про то, чтобы:
- Не заставлять классы реализовывать ненужное
- Не заставлять клиентов знать о ненужных методах
- Создавать маленькие, специализированные интерфейсы
- Строить гибкую и поддерживаемую архитектуру