【问题标题】:A problem when using the decorator design pattern使用装饰器设计模式时的一个问题
【发布时间】:2010-10-04 21:59:29
【问题描述】:

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

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,我们使用它们来抽象出第三方系统。在这种情况下,我们必须对他们的系统进行两次调用才能获取所需的数据,这就是我们进行这两次调用的地方。

【问题讨论】:

  • 你无法控制ConcreteComponentA或ConcreteComponentA包裹的第三方系统吗?
  • 是的,ConcreteComponentA 是我们的类之一。由于政治、预算等原因,在第三方系统中进行更改是困难的,而且极不可能发生这样的事情。所以 IComponent 定义了我们想要使用的单个操作,但是由于它们的服务结构,我们需要在 ConcreteComponentA 中的这个单个操作中进行多次调用。
  • 在课堂示例中,我们在用盾牌等装饰 GameCharacters 时遇到了这个问题。被装饰的对象没有“看到”它的装饰是装饰模式的一个主要缺点——其中之一。如果一个装饰器选择添加功能并且另一个装饰器包装它,隐藏第一个装饰器的功能,教科书已经指出了透明度问题。

标签: c# design-patterns decorator


【解决方案1】:

您可以创建另一个操作的重载,它将 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】:

    创建一个允许装饰器手动“覆盖” 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】:

      自调用是装饰器设计模式的局限,这是真的。 无需修改或添加任何额外基础结构即可拦截基本组件自调用的唯一方法是继承。因此,如果您不喜欢上面的解决方案并且您仍然希望拥有装饰器为您提供的灵活性(拥有任意数量和任意顺序的装饰器),您可以寻找生成子类型的动态代理的实现(即 Unity拦截,Castle 动态代理)。

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • 是的,很公平,但是在我们的例子中,我们将从服务返回的内容转换为我们自己的域级别 POCO。如果要关闭我们接口背后的系统,我们需要添加 ConcreteComponentB,然后我们必须将 CachingComponentA 重新创建为 CachingComponentB。这就是封装的好处,无论我们有多少具体的实现,我们都只有一个缓存类。
        【解决方案5】:

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

        【讨论】:

        • 我看到了这个例子在做什么,它的代码也很好:) 但是.. 除非我错了,否则不是 Evs 想要 ConcreteComponentA.AnotherOperation 调用 ConcreteDecoratorA 的原始问题。操作,但它不能。这段代码仍然有同样的问题,不是吗 - ConcreteComponentA.AnotherOperation 没有调用装饰器。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-10-05
        • 1970-01-01
        • 2020-05-13
        • 1970-01-01
        • 2016-10-05
        • 1970-01-01
        相关资源
        最近更新 更多