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

Приведи пример использования принципа Барбары Лисков

2.0 Middle🔥 131 комментариев
#ООП и паттерны

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

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

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

Принцип Барбары Лисков (Liskov Substitution Principle)

Определение

Принцип подстановки Барбары Лисков — одна из основ SOLID. Он гласит: объекты подклассов должны корректно заменять объекты базовых классов без нарушения корректности программы. Если класс S является подтипом класса T, то объекты типа T в программе могут быть заменены на объекты типа S без каких-либо нежелательных побочных эффектов.

Сиными словами: если вы создали иерархию наследования, подкласс должен работать везде, где используется родительский класс.

Плохой пример (нарушение LSP)

// Базовый класс
abstract class Bird {
  void fly();
}

// Реализация 1
class Eagle extends Bird {
  @override
  void fly() {
    print("Eagle летит высоко");
  }
}

// Реализация 2 — ПРОБЛЕМА!
class Penguin extends Bird {
  @override
  void fly() {
    throw UnsupportedError("Пингвины не летают!");
  }
}

// Использование
void makeBirdFly(Bird bird) {
  bird.fly(); // Работает для Eagle, но ПАДАЕТ для Penguin!
}

// Вызов
Bird myBird = Penguin();
makeBirdFly(myBird); // Exception: Пингвины не летают!

Проблема: Penguin нарушает контракт Bird. Код, работающий с Bird, ломается при использовании Penguin.

Правильный пример (соблюдение LSP)

// Базовый класс для всех птиц
abstract class Bird {
  void move();
  String getSpecies();
}

// Летающие птицы
abstract class FlyingBird extends Bird {
  void fly();
  
  @override
  void move() => fly();
}

// Нелетающие птицы
abstract class FlightlessBird extends Bird {
  void walk();
  
  @override
  void move() => walk();
}

// Конкретные реализации
class Eagle extends FlyingBird {
  @override
  void fly() {
    print("🦅 Eagle летит высоко в небо");
  }
  
  @override
  String getSpecies() => "Eagle";
}

class Penguin extends FlightlessBird {
  @override
  void walk() {
    print("🐧 Penguin вразвалку ходит по льду");
  }
  
  @override
  String getSpecies() => "Penguin";
}

class Parrot extends FlyingBird {
  @override
  void fly() {
    print("🦜 Parrot летит между ветками");
  }
  
  @override
  String getSpecies() => "Parrot";
}

// Корректное использование
void makeBirdMove(Bird bird) {
  bird.move(); // Работает ВСЕГДА, независимо от подкласса!
  print("Это ${bird.getSpecies()}");
}

void flyBird(FlyingBird bird) {
  bird.fly(); // Безопасно вызывать fly только для FlyingBird
}

void walkBird(FlightlessBird bird) {
  bird.walk(); // Безопасно вызывать walk только для FlightlessBird
}

// Использование
List<Bird> birds = [
  Eagle(),
  Penguin(),
  Parrot(),
];

for (var bird in birds) {
  makeBirdMove(bird); // Работает корректно для всех!
}

Практический пример из Flutter

Widget иерархия

// Неправильно: Widget может быть StatefulWidget или StatelessWidget
// но они имеют разное поведение

// Правильно:
abstract class BaseWidget extends Widget {
  final Color backgroundColor;
  
  const BaseWidget({required this.backgroundColor});
  
  @override
  Element createElement();
}

class SimpleCard extends StatelessWidget implements BaseWidget {
  @override
  final Color backgroundColor;
  
  final String title;
  
  const SimpleCard({
    required this.title,
    this.backgroundColor = Colors.white,
  });
  
  @override
  Widget build(BuildContext context) {
    return Container(
      color: backgroundColor,
      child: Text(title),
    );
  }
  
  @override
  Element createElement() => StatelessElement(this);
}

class AnimatedCard extends StatefulWidget implements BaseWidget {
  @override
  final Color backgroundColor;
  
  final String title;
  
  const AnimatedCard({
    required this.title,
    this.backgroundColor = Colors.blue,
  });
  
  @override
  State<AnimatedCard> createState() => _AnimatedCardState();
  
  @override
  Element createElement() => StatefulElement(this);
}

class _AnimatedCardState extends State<AnimatedCard>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(duration: Duration(seconds: 1), vsync: this);
    _controller.forward();
  }
  
  @override
  Widget build(BuildContext context) {
    return Container(
      color: widget.backgroundColor,
      child: ScaleTransition(
        scale: _controller,
        child: Text(widget.title),
      ),
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Пример с Repository pattern

// Общий интерфейс
abstract class UserRepository {
  Future<User> getUser(String id);
  Future<void> saveUser(User user);
}

// Реализация 1: из сети
class NetworkUserRepository implements UserRepository {
  final HttpClient _httpClient;
  
  NetworkUserRepository(this._httpClient);
  
  @override
  Future<User> getUser(String id) async {
    final response = await _httpClient.get('/users/$id');
    return User.fromJson(response);
  }
  
  @override
  Future<void> saveUser(User user) async {
    await _httpClient.post('/users', body: user.toJson());
  }
}

// Реализация 2: из локальной БД
class LocalUserRepository implements UserRepository {
  final Database _db;
  
  LocalUserRepository(this._db);
  
  @override
  Future<User> getUser(String id) async {
    final result = await _db.query('users', where: 'id = ?', whereArgs: [id]);
    if (result.isEmpty) throw Exception('User not found');
    return User.fromMap(result.first);
  }
  
  @override
  Future<void> saveUser(User user) async {
    await _db.insert('users', user.toMap());
  }
}

// Реализация 3: с кешем
class CachedUserRepository implements UserRepository {
  final UserRepository _primary;
  final Map<String, User> _cache = {};
  
  CachedUserRepository(this._primary);
  
  @override
  Future<User> getUser(String id) async {
    if (_cache.containsKey(id)) {
      return _cache[id]!;
    }
    final user = await _primary.getUser(id);
    _cache[id] = user;
    return user;
  }
  
  @override
  Future<void> saveUser(User user) async {
    _cache[user.id] = user;
    await _primary.saveUser(user);
  }
}

// Использование: код работает со ВСЕМИ реализациями!
class UserService {
  final UserRepository _repository;
  
  UserService(this._repository);
  
  Future<User> fetchUser(String id) {
    return _repository.getUser(id);
  }
}

// Инъекция зависимостей
void main() {
  // Можем использовать любую реализацию!
  final repo1 = NetworkUserRepository(httpClient);
  final repo2 = LocalUserRepository(database);
  final repo3 = CachedUserRepository(repo1);
  
  final service = UserService(repo3); // Работает одинаково!
}

Ключевые принципы LSP

  1. Контракт базового класса должен быть выполнен подклассом
  2. Исключения не должны быть неожиданными
  3. Производительность не должна деградировать в подклассе
  4. Побочные эффекты должны быть предсказуемы

Как проверить LSP в своем коде?

// ✅ Хороший знак: вы можете заменить тип везде
UserRepository repo = CachedUserRepository(NetworkUserRepository(...));
UserRepository repo2 = LocalUserRepository(...);
// Оба работают одинаково!

// ❌ Плохой знак: нужны instanceof проверки
if (repo is CachedUserRepository) {
  // Что-то специальное для кеша
} else if (repo is LocalUserRepository) {
  // Что-то специальное для локала
}

Итоги

Принцип Барбары Лисков гарантирует, что код, работающий с базовым типом, будет работать корректно с любым подтипом. Это основа полиморфизма и позволяет писать гибкий, расширяемый код. В Flutter это особенно важно при работе с Repository pattern'ом, Widget'ами и Service слоями.