← Все вопросы

Что такое инверсия зависимостей (DIP) и разделение интерфейсов (ISP) в SOLID?

Задан 29 месяцев назад167 просмотров2 ответа
7

Остались последние два принципа SOLID — DIP (Dependency Inversion) и ISP (Interface Segregation). Часто путаю инверсию зависимостей с внедрением зависимостей (DI). В чём разница? И что такое «толстый интерфейс», который запрещает ISP? Хотелось бы примеры на C#.

2 ответа

15
✓ Принятый ответ — помог автору

Разберём оба принципа и заодно развеем путаницу DIP vs DI.

Dependency Inversion Principle (DIP)

Два правила:

  1. Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба зависят от абстракций.
  2. Абстракции не зависят от деталей; детали зависят от абстракций.

Нарушение — высокоуровневый сервис жёстко завязан на конкретную реализацию:

// Плохо: OrderService прибит гвоздями к MySqlDatabase
public class MySqlDatabase
{
    public void Save(string data) { /* ... */ }
}

public class OrderService
{
    private readonly MySqlDatabase _db = new MySqlDatabase(); // жёсткая зависимость
    public void Process(string order) => _db.Save(order);
}

Инвертируем зависимость через интерфейс:

public interface IOrderStorage
{
    void Save(string data);
}

public class MySqlStorage : IOrderStorage
{
    public void Save(string data) { /* ... */ }
}

public class OrderService
{
    private readonly IOrderStorage _storage;
    // зависим от абстракции, реализацию получаем снаружи
    public OrderService(IOrderStorage storage) => _storage = storage;
    public void Process(string order) => _storage.Save(order);
}

DIP vs DI: DIP — это принцип (зависим от абстракций). Dependency Injection (DI) — это приём/механизм, как передать конкретную реализацию (через конструктор, свойство, метод или DI-контейнер). DI — один из способов реализовать DIP, но не равен ему.

Interface Segregation Principle (ISP)

«Клиенты не должны зависеть от методов, которые они не используют». Толстый интерфейс заставляет реализовывать лишнее:

// Плохо: толстый интерфейс
public interface IMachine
{
    void Print(string doc);
    void Scan(string doc);
    void Fax(string doc);
}

// Простому принтеру навязали Scan и Fax
public class SimplePrinter : IMachine
{
    public void Print(string doc) { /* ок */ }
    public void Scan(string doc) => throw new NotSupportedException();
    public void Fax(string doc) => throw new NotSupportedException();
}

Разбиваем на узкие интерфейсы:

public interface IPrinter { void Print(string doc); }
public interface IScanner { void Scan(string doc); }
public interface IFax { void Fax(string doc); }

public class SimplePrinter : IPrinter
{
    public void Print(string doc) { /* ок */ }
}

public class Mfu : IPrinter, IScanner, IFax
{
    public void Print(string doc) { }
    public void Scan(string doc) { }
    public void Fax(string doc) { }
}

Обратите внимание: появление throw new NotSupportedException() в реализации — частый сигнал нарушения сразу и ISP, и LSP.

6

По поводу путаницы: запомните формулу «DIP — это цель, DI — это средство».

  • DIP отвечает на вопрос «от чего зависеть?» — ответ: от абстракции.
  • DI отвечает на вопрос «как получить конкретную зависимость?» — ответ: пусть её передадут снаружи (обычно в конструктор).

Можно соблюдать DIP даже без DI-контейнера — достаточно принимать интерфейс параметром конструктора и собирать граф объектов вручную в Main/composition root.

Про ISP добавлю: на практике хорошим ориентиром служит то, как интерфейс используется клиентом. Если разные потребители используют разные подмножества методов — это кандидат на разделение.

Ваш ответ

Войдите, чтобы ответить на вопрос.
Поддержать проект