Повторный принцип абстракции в С#

В нашем С# MVC-приложении у нас есть много интерфейсов, которые сопоставляют 1 к 1 с объектами, которые их реализуют. т.е.: в основном для каждого созданного объекта была выполнена операция "интерфейс экстракта".

Интерфейсы используются Moq для генерации макетных объектов для наших модульных тестов. Но это то, что время и время интерфейсов повторно используются.

Никакие конкретные объекты в нашей системе не реализуют несколько интерфейсов.

Может ли кто-нибудь сказать мне, если это вызовет проблемы в будущем? И если да, каковы они будут?

Я думал, что в нашем приложении много дублирования, например, в этих двух интерфейсах (Edit: на нашем уровне сервиса) единственное, что отличается, это имя метода и тип параметра, который они принимают, но семантически они делают то же самое с репозиториями, на которые они отправляют сообщения:

interface ICustomer
{
    void AddCustomer(Customer toAdd);
    void UpdateCustomer(Customer toUpdate);
    Customer GetById(int customerId);
}

interface IEmployee
{
    void AddEmployee(Employee toBeAdded);
    void UpdateEmployee(Employee toUpdate);
    Employee GetById(int employeeId);       
}

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

public interface IEmployee: IAdd<Employee>, IUpdate<Employee>, IFinder<Employee>

Речь идет не о шаблоне репозитория - это о интерфейсах на любом уровне, которые выглядят так, как будто они имеют семантически идентичное поведение. Стоит ли вызывать общие интерфейсы для этих операций и наследовать от них "под-интерфейсы"?

По крайней мере, это обеспечило бы согласованность сигнатур методов. Но какие другие преимущества мне это дадут? (Принцип замещения Лискова в сторону)

В настоящее время имена методов и возвращаемых типов повсюду.

Я прочитал блог Марка Сееманна о повторных абстракциях Принципа, но я не понимал этого, чтобы быть откровенным. Может быть, я просто глуп:) Я также прочитал определение Fowler интерфейсов заголовков.

Ответ 1

Все это можно объединить с помощью Repository pattern...

public interface IRepository<TEntity> where TEntity : IEntity
{
    T FindById(string Id);
    T Create(T t);
    bool Update(T t);
    bool Delete(T t);
}

public interface IEntity
{
    string Id { get; set; }
} 

ИЗМЕНИТЬ

Никакие конкретные объекты в нашей системе не реализуют несколько интерфейсов.

Может ли кто-нибудь сказать мне, если это вызовет проблемы в будущем? И если да, каковы они будут?

Да, это вызовет проблемы, если он еще не начал это делать.

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

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

Вы суммировали это,

Речь идет не о шаблоне репозитория - это о интерфейсах на любом уровне, которые выглядят так, как будто они имеют семантически идентичное поведение. Стоит ли вызывать общие интерфейсы для этих операций и наследовать от них "под-интерфейсы"?

Это не о interfaces, это около abstractions, Repository pattern демонстрирует, как вы можете абстрагировать поведение, адаптированное к определенному объекту.

В приведенном выше примере нет методов с именем AddEmployee или UpdateEmployee... такими методами являются только мелкие интерфейсы, а не абстракции.

Концепция Repository pattern очевидна в том, что она определяет набор поведений, которые реализуются рядом различных классов, каждый из которых предназначен для конкретной сущности.

Учитывая, что репозиторий реализуется для каждого объекта (UserRepository, BlogRepository и т.д.) и учитывая, что каждый репозиторий должен поддерживать основной набор функциональных возможностей (основные операции CRUD), мы можем взять этот основной набор функций и определить его в интерфейс, а затем реализовать этот интерфейс в каждом репозитории.

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

public interface IVehicleOperator<TVehicle> where TVehicle : IVehicle
{
    void Accelerate();
    void Brake();
}

При этом у нас больше нет отображений 1:1, а вместо фактической абстракции.

Пока мы обсуждаем эту тему, возможно, стоит рассмотреть и decorator pattern.

Ответ 2

Учитывая это:

interface ICustomer{
    void AddCustomer(Customer toAdd);
    void UpdateCustomer(Customer toUpdate);
    Customer GetById(int customerId);
}

interface IEmployee
{
    void AddEmployee(Employee toBeAdded);
    void UpdateEmployee(Employee toUpdate);
    Employee GetById(int employeeId);       
}

Я бы, наверное, начинал с его перепроектирования следующим образом:

interface IRepository<T>
{
    void Add(T toAdd);
    void Update(T toUpdate);
    T GetById(int id);
}

Однако это может все-таки очень нарушать принцип интерфейса сегрегации, не говоря уже о том, что поскольку он также нарушает Command-Query Responsibility Segregation (а не архитектура) также не может быть ни коварным, ни контравариантным.

Таким образом, моим следующим шагом может быть разделение их на Ролевые интерфейсы:

interface IAdder<T>
{
    void Add(T toAdd);
}

interface IUpdater<T>
{
    void Update(T toAdd);
}

interface IReader<T>
{
    T GetById(int id);
}

Кроме того, вы можете заметить, что IAdder<T> и IUpdater<T> структурно идентичны (они только семантически разные), поэтому почему бы не сделать их одним:

interface ICommand<T>
{
    void Execute(T item);
}

Чтобы сохранить согласованность, вы также можете переименовать IReader<T>:

interface IQuery<T>
{
    T GetById(int id);
}

По сути, вы можете свести все к этим двум интерфейсам, но для некоторых людей это может быть слишком абстрактным и нести слишком мало семантической информации.

Однако я не думаю, что можно дать лучший ответ, потому что предпосылка неверна. Первоначальный вопрос заключается в том, как должен быть разработан интерфейс, но клиент нигде не отображается. Как APPP ch. 11 учит нас, "клиентов [...] владеют абстрактными интерфейсами" - другими словами, клиент определяет интерфейс, исходя из того, что ему нужно. Интерфейсы не должны извлекаться из конкретных классов.

Дальнейшие учебные материалы по этому вопросу: