使用装饰器设计模式时的问题

时间:2010-10-04 21:59:29

标签: c# design-patterns decorator

我们目前正在使用装饰器设计模式来执行一些缓存。所以我们有一堆看起来像这样的类:

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;
  }
}

问题是当从AnotherOperation()方法中调用Operation()方法时,永远不会调用装饰器实现,因为很明显装饰器不在ConcreteComponentA的继承层次结构中。

那么我们在某处做出了糟糕的设计决定,还是这只是我们必须接受的装饰设计模式的限制?

请注意,在我的真实示例中,ConcreteComponentA是我们无法控制的第三方系统的包装器。我们已经开发了IComponent和一堆POCO,我们与之合作来抽象出第三方系统。在这种情况下,我们必须对他们的系统进行两次调用才能获得所需的数据,这只是我们进行这两次调用的地方。

5 个答案:

答案 0 :(得分:4)

创建一个委托(或者如果你想支持多个装饰器的事件),允许装饰者手动“覆盖”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;
        }
    }
}

使用一次性模式确保在不再需要装饰器时解除事件,以防止内存泄漏。

答案 1 :(得分:3)

你可以创建一个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);
  }
}

答案 2 :(得分:3)

自我调用是装饰设计模式的限制,这是真的。 拦截基本组件自调用而不必修改它或添加任何其他基础结构的唯一方法是继承。因此,如果您不喜欢上面的解决方案,并且您仍然希望获得装饰器为您提供的灵活性(可能具有任何数量和任何装饰器顺序),您可以查找生成子类型的动态代理的实现(即Unity拦截,城堡动态代理)。

答案 3 :(得分:1)

我更喜欢使用继承而不是封装来进行缓存,这样,缓存的值将使用缓存方法,因为它是虚拟的:

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()将使用装饰器类。

答案 4 :(得分:0)

由于您可以控制两个级别(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()
  {

  }
}