Украшение с несколькими интерфейсом - PullRequest
6 голосов
/ 14 марта 2019

Я всегда борюсь с оформлением + интерфейсами.Допустим, у меня есть следующие интерфейсы «поведения»:

interface IFlyable { void Fly();}
interface ISwimmable { void Swim();}

Основной интерфейс

interface IMainComponent { void DoSomethingA(); void DoSomethingB();}

Декоратор на главном интерфейсе

    public class Decorator : IMainComponent
    {
        private readonly IMainComponent decorated;
        [..]

        public virtual void DoSomethingA()
        {
            decorated.DoSomethingA();
        }

        public virtual void DoSomethingB()
        {
            decorated.DoSomethingB();
        }
    }

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

    public class Decorator : IMainComponent, IFlyable, ISwimmable
    {
        [..]

        public virtual void Fly()
        {
            ((IFlyable)decorated).Fly();
        }

        public virtual void Swim()
        {
            ((ISwimmable)decorated).Swim();
        }

Но мне это не нравится, потому что:

  1. Может показаться, что "Декоратор" реализует интерфейс, в то время какэто не так (исключение приведения во время выполнения)
  2. Это не масштабируется, мне нужно добавлять каждый новый интерфейс (и не забывать об этом добавлении)

Другое решениеэто добавить «ручное приведение», которое распространяется через дерево декораций:

    public class Decorator : IMainComponent
    {
        public T GetAs<T>()
            where T : class
        {
            //1. Am I a T ?
            if (this is T)
            {
                return (T)this;
            }

            //2. Maybe am I a Decorator and thus I can try to resolve to be a T
            if (decorated is Decorator)
            {
                return ((Decorator)decorated).GetAs<T>();
            }

            //3. Last chance
            return this.decorated as T;
        }

Но проблемы таковы:

  1. Вызывающая сторона может манипулировать обернутым объектом после вызоваGETAS ().
  2. Это может привести к путанице / нежелательному поведению, если использовать метод из IMainComponent после вызова GetAs (что-то вроде ((IMainComponent) GetAs ()). DoSomethingB (); ==> это может вызвать реализациюзавернутый объект, а не полное оформление.
  3. Необходимо вызвать метод GetAs (), и выход из кода с приведением / обычным "As" не будет работать.

Как вамразрешить / разрешить эту проблему? Существует ли шаблон для решения этой проблемы?

PD: мой вопрос касается окончательной реализации C #, но, возможно, решение более широкое.

Ответы [ 3 ]

3 голосов
/ 15 марта 2019

Вам нужно будет создать отдельные декораторы для каждого интерфейса.Альтернативой является использование универсального интерфейса для ваших услуг и универсального декоратора.Например:

public interface ICommandService<TCommand>
{
   Task Execute(TCommand command);
}

public class LoggingCommandService<TCommand> : ICommandService<TCommand>
{
  public LoggingCommandService(ICommandService<TCommand> command, ILogger logger)
  {
    ...
  }

  public async Task Execute(TCommand command)
  {
    // log
    await this.command.Execute(command);
    // log
  }
}
1 голос
/ 24 марта 2019

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

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

Если вы запрашиваете реализацию, я бы рекомендовал использовать инфраструктуру внедрения зависимостей.На рынке их много, таких как MEF, Ninject, Unity, Windsor, DryIoC, SimpleInject, LightInjector, Grace, Stashbox, ... и это лишь некоторые из них, которые приходят мне в голову.

СутьДекоратор - это нечто совершенно другое.Декоратор используется, если вы НЕ просто перенаправляете вызовы интерфейса, но добавляете к нему некоторую дополнительную логику (например, повторные попытки).В этом случае вы все равно ограничитесь методами оригинального интерфейса.

0 голосов
/ 14 марта 2019

Шаблон Decorator не предназначен для добавления новых методов в декорируемый объект. Это то, что вы пытаетесь сделать, и это невозможно сделать элегантно на языке статической типизации.

Для чего нужен Decorator, когда интерфейс к декораторам и декорированному компоненту одинаков, а декоратор добавляет немного дополнительной функциональности в метод либо до, либо после передачи запроса по цепочке декораторов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...