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

Приведи пример использования Liskov Substitution Principle

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

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

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

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

Принцип подстановки Лисков (Liskov Substitution Principle)

Это один из пяти принципов SOLID. Суть: объекты подклассов должны корректно заменять объекты суперклассов без нарушения работы программы.

Неправильный пример

class Bird {
  fly(): void {
    console.log("Птица летит");
  }
}

class Penguin extends Bird {
  fly(): void {
    throw new Error("Пингвин не может летать!");
  }
}

// Нарушение LSP
function makeBirdFly(bird: Bird) {
  bird.fly(); // Может выбросить ошибку для Penguin
}

makeBirdFly(new Penguin()); // Неожиданное исключение

Здесь Penguin нарушает контракт Bird, т.к. не может летать.

Правильное решение 1: Правильная иерархия

abstract class Bird {
  abstract move(): void;
}

class FlyingBird extends Bird {
  move(): void {
    this.fly();
  }
  
  private fly(): void {
    console.log("Летит в небе");
  }
}

class Penguin extends Bird {
  move(): void {
    this.swim();
  }
  
  private swim(): void {
    console.log("Плывёт в воде");
  }
}

function makeMove(bird: Bird) {
  bird.move(); // Работает для всех птиц
}

makeMove(new FlyingBird()); // Летит в небе
makeMove(new Penguin());     // Плывёт в воде

Правильное решение 2: Interface Segregation

interface Flyer {
  fly(): void;
}

interface Swimmer {
  swim(): void;
}

class Eagle implements Flyer {
  fly(): void {
    console.log("Орёл летит высоко");
  }
}

class Penguin implements Swimmer {
  swim(): void {
    console.log("Пингвин плывёт");
  }
}

class Duck implements Flyer, Swimmer {
  fly(): void {
    console.log("Утка летит");
  }
  
  swim(): void {
    console.log("Утка плывёт");
  }
}

function makeFly(flyer: Flyer) {
  flyer.fly();
}

function makeSwim(swimmer: Swimmer) {
  swimmer.swim();
}

makeFly(new Eagle());   // Работает
makeFly(new Duck());    // Работает
makeSwim(new Penguin()); // Работает
makeSwim(new Duck());   // Работает

Пример с хранилищем данных

interface DataStore {
  get(id: string): Promise<any>;
  set(id: string, data: any): Promise<void>;
}

class PostgresStore implements DataStore {
  async get(id: string): Promise<any> {
    return { id, data: "из Postgres" };
  }
  
  async set(id: string, data: any): Promise<void> {
    // Сохранение в БД
  }
}

class RedisCache implements DataStore {
  async get(id: string): Promise<any> {
    return { id, data: "из Redis" };
  }
  
  async set(id: string, data: any): Promise<void> {
    // Кэширование
  }
}

class InMemoryStore implements DataStore {
  private data = new Map();
  
  async get(id: string): Promise<any> {
    return this.data.get(id);
  }
  
  async set(id: string, data: any): Promise<void> {
    this.data.set(id, data);
  }
}

async function cacheUserData(store: DataStore, userId: string) {
  const user = await store.get(userId);
  await store.set(userId, { ...user, lastSeen: new Date() });
}

cacheUserData(new PostgresStore(), "123");
cacheUserData(new RedisCache(), "123");
cacheUserData(new InMemoryStore(), "123");

Ключевые признаки нарушения LSP

  1. Type checking перед использованием — специальная обработка для подтипов
  2. Empty implementations — методы ничего не делают
  3. Выброс исключений — метод выбрасывает исключение вместо корректной работы

Практическое применение

  • Правильная иерархия классов — наследуй только если есть отношение является
  • Interface Segregation — разделяй большие интерфейсы на маленькие
  • Dependency Injection — зависит от абстракций
  • Unit тестирование — легко подменять реальные объекты на mock'и

Соблюдение LSP делает код более гибким, тестируемым и устойчивым к изменениям.