Украшение 1 из нескольких реализованных интерфейсов - PullRequest
0 голосов
/ 13 июля 2020

Мне интересно, возможно ли иметь декоратор для одного из нескольких реализованных интерфейсов в C#. Я склоняюсь к «нет», но возможно.

Вот что я имею в виду

public abstract class Auditable
{
    public string CreatedBy { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime ModifiedAt { get; set; }
    public string ModifiedBy { get; set; }
}

public class MyClass : Auditable
{
  // <...> properties
}


public interface IWriteRepository<T> : where T : Auditable
{
    T Create(T entity);
    T Update(T entity);
}

public class AuditRepositoryDecorator<T> : IWriteRepository<T> where T : Auditable
{
    private readonly IWriteRepository<T> _decorated;

    // <...> ctor with injects

    public T Create(T entity)
    {
        entity.ModifiedAt = time;
        entity.CreatedAt = time;
        entity.CreatedBy = invoker;
        entity.ModifiedBy = invoker;

        return _decorated.Create(entity);
    }

    public T Update(T entity)
    {
        entity.ModifiedAt = time;
        entity.ModifiedBy = invoker;

        return _decorated.Update(entity);
    }
}


public interface IMyClassRepository : IWriteRepository<MyClass>
{
     MyClass Get(int id);
}

Итак, я хотел бы иметь возможность полагаться на репозиторий IMyClassRepository и всякий раз, когда Create или Update будет вызываться, это будет от go до AuditRepositoryDecorator. Это кусок logi c, который часто выполняется, и я думаю, что было бы намного проще использовать его в качестве декоратора вместо того, чтобы иметь отношение композиции к некоторому интерфейсу, который делает то же самое.

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

Я использую структуру DI dnc2.1 по умолчанию с Скрутор для украшений.

Ответы [ 2 ]

2 голосов
/ 13 июля 2020

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

Предположим, у вас есть IMyClassRepository потребитель:

public class RepoConsumer
{
    RepoConsumer(IMyClassRepository repo) ...
}

И IMyClassRepository реализация:

public class MyClassRepositoryImpl : IMyClassRepository
{
    ...
}

Теперь давайте создадим граф объектов для RepoConsumer, который использует AuditRepositoryDecorator<MyClass>:

var repo = new MyClassRepositoryImpl();
var decoratedRepo = new AuditRepositoryDecorator<MyClass>(repo);
var consumer = new RepoConsumer(decoratedRepo); // <-- COMPILE ERROR

Когда вы скомпилируете этот код, вы заметите, что компилятор C# сгенерирует ошибку строка new RepoConsumer. Это потому, что RepoConsumer ожидает IMyClassRepository. Хотя MyClassRepositoryImpl реализует IMyClassRepository, AuditRepositoryDecorator<MyClass> не реализует IMyClassRepository.

Чтобы решить эту проблему, вы можете попробовать разрешить AuditRepositoryDecorator<T> реализовать IMyClassRepository, но это будет очевидно, будет некрасиво, потому что декоратор должен будет реализовать дюжину интерфейсов для каждого объекта в вашей системе.

Но это упражнение доказывает, что проблема не столько в контейнере DI, сколько в что система типов просто не позволяет вам построить граф этого объекта. А поскольку система типов не позволяет этого, контейнер DI определенно не позволит этого. Он не может обойти проверку типов в системе типов. К счастью.

Но решение вашей проблемы на самом деле действительно простое: удалите указанные c IMyClassRepository и позвольте потребителям зависеть от IWriteRepository<MyClass>. Это может показаться неутешительным решением, но существует множество проблем, связанных с производством от общих c интерфейсов. Просто примите тот факт, что потребители зависят от такой обобщенной c абстракции. Это займет некоторое время, но со временем вы начнете любить и ценить этот стиль программирования.

Но, конечно, это все еще оставляет нам вопрос о том, как добавить новые методы, такие как MyClass Get(string) . Существует несколько решений, таких как:

  • Реализовать его как метод расширения (возможно только тогда, когда сам метод требует доступа к самому интерфейсу, а не к внутренним компонентам класса)
  • Определить отдельный интерфейс, что может быть хорошей идеей в целом, в соответствии с Принципом разделения интерфейсов
1 голос
/ 13 июля 2020

наиболее используемый подход в этих случаях - это шаблон репозитория, как объясняется здесь в моем ответе: Как мне избежать повторения кода при определении класса функций, которые изменяют только тип данных обрабатываемых параметров?

в вашем случае это иерархия классов:

public interface IWriteRepository<T> : where T : Auditable
{
   T Create(T entity);
   T Update(T entity);
}

public abstract class WriteRepositoryBase<T> : IWriteRepository<T> where T : Auditable
{
    //implement create and update
}
public interface IMyRepository : IWriteRepository<MyClass>
{
     MyClass Get(string id);
}

public class MyRepository : WriteRepositoryBase<MyClass>, IMyRepository
{
     //implement Get
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...