Проблема при использовании шаблона оформления декоратора - PullRequest
8 голосов
/ 05 октября 2010

В настоящее время мы используем шаблон проектирования декоратора для выполнения некоторого кэширования.Итак, у нас есть несколько классов, которые выглядят примерно так:

interface IComponent
{
  object Operation();
  object AnotherOperation();
}
public ConcreteComponentA : IComponent
{
  public object Operation()
  {
    return new object();
  }
  public object AnotherOperation()
  {
    return new object();
  }
}
public ConcreteDecoratorA : IComponent
{
  protected IComponent component;
  public object Operation()
  {
    if(!this.cache.Contains("key")
    {
      this.cache["key"] = this.component.Operation();
    }
    return this.cache["key"];
}

Так что, если клиент хочет использовать кэширование, он создаст новый ConcreteDecoratorA и передаст в ConcreteComponentA конструктор.Проблема, с которой мы сталкиваемся, - представьте, что AnotherOperation () требует вызова Operation для выполнения своей работы.Теперь ConcreteComponentA может выглядеть примерно так:

public ConcreteComponentA : IComponent
{
  public object Operation()
  {
    return new object();
  }
  public object AnotherOperation()
  {
    object a = this.Operation();
    // Do some other work
    return a;
  }
}

Проблема заключается в том, что при вызове метода Operation () из метода AnotherOperation () реализация декоратора никогда не будет вызвана, поскольку очевидно, что декоратор не находится виерархия наследования ConcreteComponentA.

Итак, мы где-то приняли плохое дизайнерское решение или это просто ограничение шаблона проектирования декоратора, которое мы должны принять?

Обратите внимание, что в моем примере из реального мира, ConcreteComponentA - это оболочка для сторонней системы, которую мы не можем контролировать.Мы разработали IComponent и несколько POCO, с которыми работаем, чтобы абстрагировать эту стороннюю систему.В этом случае нам нужно сделать два вызова в их систему, чтобы получить необходимые данные, и это примерно то, где мы делаем эти два вызова.

Ответы [ 5 ]

4 голосов
/ 05 октября 2010

Создайте делегат (или событие, если вы хотите поддерживать несколько декораторов), который позволяет декораторам вручную "переопределять" метод Operation.

public class ConcreteComponentA : IComponent
{
    public event Func<object> OperationOverride;

    public object Operation()
    {
        if (OperationOverride != null)
        {
            return OperationOverride();
        }
        return new object();
    }

    public object AnotherOperation()
    {
        var a = Operation();
        // Do some other work
        return a;
    }
}

В конструкторе декоратора попытайтесь привести экземпляр компонента втип вашего конкретного компонента и присоедините делегат переопределения операции.

public class ConcreteDecoratorA : IComponent, IDisposable
{
    protected readonly IComponent component;

    public ConcreteDecoratorA(IComponent component)
    {
        this.component = component;
        AttachOverride();
    }

    public void Dispose()
    {
        DetachOverride();
    }

    private void AttachOverride()
    {
        var wrapper = component as ConcreteComponentA;
        if (wrapper != null)
        {
            wrapper.OperationOverride += Operation;
        }
    }

    private void DetachOverride()
    {
        var wrapper = component as ConcreteComponentA;
        if (wrapper != null)
        {
            wrapper.OperationOverride -= Operation;
        }
    }
}

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

3 голосов
/ 06 октября 2010

Самовызовы - это ограничение шаблона дизайна декоратора, это правда. Единственный способ перехватить собственные вызовы базового компонента без необходимости его изменения или добавления какой-либо дополнительной инфраструктуры - это наследование. Так что, если вам не нравятся решения сверху и вы все еще хотите иметь гибкость, которую дает вам декоратор (возможность иметь любое количество и любой порядок декораторов), вы можете искать реализацию динамического прокси, который генерирует подтипы (т.е. Перехват, Castle Dynamic Proxy).

3 голосов
/ 05 октября 2010

Вы можете создать перегрузку AnotherOperation, которая принимает IComponent для использования в качестве параметра.

public ConcreteComponentA : IComponent
{
  public object Operation()
  {
    return new object();
  }
  public object AnotherOperation()
  {
    return AnotherOperation(this);
  }
  public object AnotherOperation(IComponent comp)
  {
    object a = comp.Operation();
    // Do some other work
    return a;
  }
}

public ConcreteDecoratorA : IComponent
{
  protected IComponent component;
  public object Operation()
  {
    if(!this.cache.Contains("key")
    {
      this.cache["key"] = this.component.Operation();
    }
    return this.cache["key"];
  }
  public object AnotherOperation()
  {
    return this.component.AnotherOperation(this);
  }
}
1 голос
/ 06 октября 2010

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

public ConcreteComponentA : IComponent
{
  public virtual object Operation()
  {
    return new object();
  }
  public object AnotherOperation()
  {
    object a = this.Operation();
    // Do some other work
    return a;
  }
}


public CachingComponentA : ConcreteComponentA
{
     public override object Operation()
     {
         if(!this.cache.Contains("key")
         {
            this.cache["key"] = base.Operation();
         }
         return this.cache["key"];
     }
}

Затем, когда вы используете объект декоратораthis.Operation () будет использовать класс декоратора.

0 голосов
/ 05 октября 2010

Так как у вас есть контроль над обоими уровнями (ConcreteComponentA и ConcreteDecoratorA), вы можете иметь их ручные заметки туда и обратно:

interface IComponent 
{
  Action<object> myNotify;
  object Operation(); object AnotherOperation(); 
} 

public ConcreteComponentA : IComponent
{
  public Action<object> myNotify = null;
  public object Operation()
  {
    object result = new object();
    if (myNotify != null)
    {
      myNotify(result);
    }
    return result;
  }

  public object AnotherOperation()
  {
    return Operation();
  }
}

public ConcreteDecoratorA : IComponent
{
  public ConcreteDecoratorA(IComponent target)
  {
    component = target;
    target.myNotify = notifyMe;
  }
  protected IComponent component;
  protected notifyMe(object source)
  {
    this.cache["key"] = source;
  }

  public Action<object> myNotify = null;
  public object Operation()
  {
    if(!this.cache.Contains("key")
    {
      return component.Operation();
    }
    return this.cache["key"];
  }
  public object AnotherOperation()
  {

  }
}
...