Приведи пример использования принципа разделения интерфейсов (Interface Segregation Principle) в Dart.

Ответ

Принцип разделения интерфейсов (ISP) утверждает, что клиенты не должны зависеть от методов, которые они не используют. В Dart это реализуется через создание небольших, специфичных интерфейсов (абстрактных классов), а не одного громоздкого.

Пример нарушения ISP:

// Один интерфейс для всех сущностей, заставляющий реализовывать ненужные методы.
abstract class Worker {
  void code();
  void test();
  void deploy();
  void cook();
}

class Developer implements Worker {
  @override
  void code() => print('Writing Flutter app');

  @override
  void test() => print('Running unit tests');

  @override
  void deploy() => print('Deploying to Firebase');

  @override
  void cook() => throw UnimplementedError('Developer cannot cook!'); // Ненужный метод!
}

Пример следования ISP:

// Разделяем на логичные, узкие интерфейсы.
abstract class Codeable {
  void code();
}

abstract class Testable {
  void test();
}

abstract class Deployable {
  void deploy();
}

abstract class Cookable {
  void cook();
}

// Классы реализуют только нужные им интерфейсы.
class FlutterDeveloper implements Codeable, Testable, Deployable {
  @override
  void code() => print('Writing Flutter app with Dart');

  @override
  void test() => print('Running widget tests');

  @override
  void deploy() => print('Building APK/IPA and deploying to store');
}

class Chef implements Cookable {
  @override
  void cook() => print('Cooking dinner');
}

Преимущества:

  • Избегаем «жирных» интерфейсов. Классы не загрязнены нерелевантными методами.
  • Повышаем стабильность. Изменения в одном интерфейсе (например, Deployable) затрагивают только классы, которые от него зависят, а не всех «работников».
  • Упрощаем тестирование и понимание. Легче мокать и тестировать небольшие контракты.

Ответ 18+ 🔞

А, ну это тот самый принцип, когда тебе впаривают один здоровенный интерфейс, а ты потом сидишь и думаешь: «Нахуя мне этот метод, я его никогда не вызываю?». Это как если бы тебе в машину встроили швейную машинку. Ну, типа, вдруг пригодится? А на деле она только место занимает и ты про неё вечно спотыкаешься.

Вот смотри, какой пиздец может получиться, если не следовать этому принципу.

// Один интерфейс для всех сущностей, заставляющий реализовывать ненужные методы.
abstract class Worker {
  void code();
  void test();
  void deploy();
  void cook();
}

class Developer implements Worker {
  @override
  void code() => print('Writing Flutter app');

  @override
  void test() => print('Running unit tests');

  @override
  void deploy() => print('Deploying to Firebase');

  @override
  void cook() => throw UnimplementedError('Developer cannot cook!'); // Ненужный метод!
}

Представляешь? Разработчик, который должен готовить. Это ж ёперный театр! Он либо сгорит на кухне, либо выбросит исключение, как у нас в коде. И каждый раз, когда ты создаёшь нового Developer, ты должен про эту проклятую готовку помнить. Доверия к такому интерфейсу — ноль ебать. Подозрение ебать чувствую, что его писал какой-нибудь архитектор, который в жизни код не писал.

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

// Разделяем на логичные, узкие интерфейсы.
abstract class Codeable {
  void code();
}

abstract class Testable {
  void test();
}

abstract class Deployable {
  void deploy();
}

abstract class Cookable {
  void cook();
}

// Классы реализуют только нужные им интерфейсы.
class FlutterDeveloper implements Codeable, Testable, Deployable {
  @override
  void code() => print('Writing Flutter app with Dart');

  @override
  void test() => print('Running widget tests');

  @override
  void deploy() => print('Building APK/IPA and deploying to store');
}

class Chef implements Cookable {
  @override
  void cook() => print('Cooking dinner');
}

Вот теперь красота! FlutterDeveloper — он Codeable, Testable и Deployable. И всё. Он не Cookable. Он не должен быть. Пусть шеф готовит, у него это лучше получается. Каждый занимается своим делом, интерфейсы маленькие и понятные.

И какие плюсы-то, спросишь?

  • Жирных интерфейсов нет. Класс не превращается в свалку ненужных методов, которые только путают. Никаких UnimplementedError из-за того, что тебя заставили готовить.
  • Стабильность выше крыши. Захотел поменять что-то в процессе деплоя? Меняешь интерфейс Deployable и всё. Тебя не ебут все подряд классы в проекте, а только те, кто реально деплоит. Никаких сюрпризов.
  • Тестировать и понимать — одно удовольствие. Представь, нужно замокать объект для теста. Если у него методов овердохуища, это ад. А если три маленьких интерфейса — да похуй, сделал мок для Codeable и спи спокойно. Код читается как книга, потому что сразу видно, что умеет твой класс, а что нет.

Короче, смысл в том, чтобы не создавать этих монстров на все случаи жизни. Лучше десять маленьких и точных интерфейсов, чем один большой, от которого всем тошно.