Повторный принцип абстракции в С#
В нашем С# 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 учит нас, "клиентов [...] владеют абстрактными интерфейсами" - другими словами, клиент определяет интерфейс, исходя из того, что ему нужно. Интерфейсы не должны извлекаться из конкретных классов.
Дальнейшие учебные материалы по этому вопросу: