SOLID: Принцип разделения интерфейсов (ISP) в C#

Принцип разделения интерфейсов (ISP) — это один из пяти принципов SOLID, сформулированный следующим образом: клиенты не должны реализовывать методы, которые они не используют. Что означает ISP и как он реализуется в C# — рассмотрим в этой части.

Принцип разделения интерфейсов (ISP) в C#

Ключевая идея ISP: вместо одного большого интерфейса предпочтительны многочисленные маленькие интерфейсы, основанные на группах методов

Чтобы разобраться в принципе разделения интерфейсов, разберем его формулировку не две части:

  1. Во-первых, ни один класс не должен быть вынужден реализовывать какие-либо методы интерфейса, которые он не использует.
  2. Во-вторых, вместо создания больших или, можно сказать, громоздких интерфейсов создавайте несколько небольших интерфейсов, чтобы клиенты думали только о методах, которые они хотят реализовать.

Рассмотрим применение принципа разделения интерфейсов в C# на примере.

Пример использования принципа разделения интерфейсов (ISP) в C#

Пример без использования принципа разделения интерфейсов (ISP) в C#

Допустим, мы разрабатываем интерфейс для работы с данными в БД. В результате мы получаем вот такой интерфейс:

public interface ICrudService<T>
{
    T Create(T entity);
    T Read(int id);
    T Update(T entity);
    void Delete(int id);
    void Validate(T entity);
    void SendNotification(T entity, string message);
}

Теперь реализуем этот интерфейс в нескольких классах:

public class TasksRepository<T> : ICrudService<T>
{
    public T Create(T entity)
    {
        //код создания задачи
    }

    public void Delete(int id)
    {
        //код удаления задачи
    }

    public T Read(int id)
    {
        //код получения задачи
    }

    public void SendNotification(T entity, string message)
    {
        //код отправки уведомления
    }

    public T Update(T entity)
    {
        //код обновления задачи
    }

    public void Validate(T entity)
    {
        //код проверки задачи
    }
}

public class DataRepository<T> : ICrudService<T>
{
    public T Create(T entity)
    {
        //код создания данных
    }

    public void Delete(int id)
    {
        //код удаления данных
    }

    public T Read(int id)
    {
        //код чтения данных
    }

    public void SendNotification(T entity, string message)
    {
        //этот метод нам не нужен
        throw new NotImplementedException();
    }

    public T Update(T entity)
    {
        //этот метод нам не нужен
        throw new NotImplementedException();
    }

    public void Validatw(T entity)
    {
        throw new NotImplementedException();
    }
}

public class MessageRepository<T> : ICrudService<T>
{
    public T Create(T entity)
    {
        //код создания данных
    }

    public void Delete(int id)
    {
        //этот метод нам не нужен
        throw new NotImplementedException();
    }

    public T Read(int id)
    {
        //этот метод нам не нужен
        throw new NotImplementedException();
    }

    public void SendNotification(T entity, string message)
    {
        //код отправки уведомления
    }

    public T Update(T entity)
    {
        //этот метод нам не нужен
        throw new NotImplementedException();
    }

    public void Validate(T entity)
    {
        //этот метод нам не нужен
        throw new NotImplementedException();
    }
}

Как можно видеть по представленному выше примеру, каждый класс реализует свои методы:Принцип разделения интерфейсов (ISP) в C#

Это наглядная демонстрация нарушения принципа разделения интерфейсов — наш интерфейс ICrudService получился «и швец, и жнец, и на дуде игрец» — делает всё.  В результате, наши классы должны реализовывать те методы, которые им вообще не требуются. Следовательно, чтобы соответствовать ISP, нам необходимо разбить этот интерфейс на несколько более мелких.

Примечание: прежде, чем дробить интерфейс на более мелкие интерфейсы, необходимо провести анализ исходного кода и определить, какие методы в какой интерфейс поместить. Это может быть не самая простая задача. Более того, ISP не предполагает, что у нас в коде обязательно должно соблюдаться условие «один интерфейс = один метод». Интерфейс может содержать и группу методов. Главное, чтобы это не приводило к нарушению принципа

Пример c использованием принципа разделения интерфейсов (ISP) в C#

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

public interface ICreateble<T>
{
    T Create(T entity);
}

public interface IReadable<T>
{
    T Read(int id);
}

public interface IUpdatable<T>
{
    T Update(T entity);
}

public interface IDeletable<T>
{
    void Delete(int id);
}

public interface IValidable<T>
{
    void Validate(T entity);
}

public interface INotifiable<T>
{
    void SendNotification(T entity, string message);
}


public class TasksRepository<T> : ICreateble<T>, IReadable<T>, IUpdatable<T>, INotifiable<T>, IValidable<T>, IDeletable<T>
{
    public T Create(T entity)
    {
        //код создания задачи
    }

    public void Delete(int id)
    {
        //код удаления задачи
    }

    public T Read(int id)
    {
        //код получения задачи
    }

    public void SendNotification(T entity, string message)
    {
        //код отправки уведомления
    }

    public T Update(T entity)
    {
        //код обновления задачи
    }

    public void Validate(T entity)
    {
        //код проверки задачи
    }
}

public class DataRepository<T> : ICreateble<T>, IDeletable<T>, IReadable<T>
{
    public T Create(T entity)
    {
        //код создания данных
    }

    public void Delete(int id)
    {
        //код удаления данных
    }

    public T Read(int id)
    {
        //код чтения данных
    }
}

public class MessageRepository<T> : ICreateble<T>, INotifiable<T>
{
    public T Create(T entity)
    {
        //код создания данных
    }

    public void SendNotification(T entity, string message)
    {
        //код отправки уведомления
    }

}

В данном случае, мы разделили наш интерфейс на более мелки, в каждом из которых мы определили по одному методу. Стоит ещё раз особенно подчеркнуть — такое разделение не всегда требуется и не является обязательным. Вполне возможно, что при анализе вашего кода, вам будет достаточно сосредоточить все операции CRUD в одном интерфейсе, а проверку и отправку уведомлений — в двух других интерфейсах. Всё зависит от конкретной ситуации.

Преимущества принципа разделения интерфейсов в C#

Ниже перечислены некоторые из ключевых преимуществ применения принципа разделения интерфейсов в C#:

  • Уменьшение побочных эффектов: благодаря разделению интерфейсов изменения в одной части системы с меньшей вероятностью повлияют на компоненты, реализующие эти интерфейсы. Клиенты будут знать только о тех методах, которые имеют к ним отношение, поэтому изменения в несвязанных методах не повлияют на них.
  • Повышенная ясность и сосредоточенность: небольшие, четко определенные интерфейсы легче понять и реализовать. Каждый интерфейс фокусируется на определенном аспекте, что помогает разработчикам быстро понять, что требуется, не тратя время на изучение ненужных функций.
  • Упрощенное обслуживание и расширение: благодаря небольшим разделенным интерфейсам обслуживание и расширение программного обеспечения становятся более простыми. Разработчики могут добавлять новые функции, не изменяя существующие интерфейсы, просто создавая новые интерфейсы и реализуя классы с несколькими интерфейсами по мере необходимости.
  • Повышенная возможность повторного использования: Разделенные интерфейсы повышают возможность повторного использования. Поскольку каждый интерфейс меньше по размеру и более специфичен, его можно с большей легкостью использовать в разных частях системы или даже в разных проектах без лишних зависимостей.
  • Способствует разделению: ISP помогает уменьшить связь между классами. Это позволяет реализующим классам зависеть только от тех интерфейсов, которые они фактически используют, что минимизирует зависимости между компонентами.
Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии