2010-10-04 198 views
7

我们目前正在使用装饰设计模式来执行一些缓存。因此,我们有一堆看起来像这样的类:使用装饰设计模式时出现问题

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()方法调用运行()方法时,装饰实现将永远不会被调用,因为很明显的装饰是不是在继承层次ConcreteComponentA。

所以我们在某个地方做了一个糟糕的设计决定,或者这只是我们必须接受的装饰设计模式的一个限制吗?

请注意,在我的真实世界示例中,ConcreteComponentA是我们无法控制的第三方系统的包装。我们已经开发出了IComponent和一系列POCO,我们将它们用于抽象出第三方系统。在这种情况下,为了获得所需的数据,我们必须对其系统进行两个调用,这就是我们进行这两个调用的位置。

+0

您是否无法控制ConcreteComponentA或由ConcreteComponentA包装的第三方系统? – dtb 2010-10-04 22:06:13

+0

是的,ConcreteComponentA是我们的课程之一。由于政治,预算等原因,在第三方系统中改变事情是困难的,而且对于这样的事情极不可能。所以IComponent定义了我们想要使用的单个操作,但由于其服务的结构,我们需要在ConcreteComponentA中的单个操作中进行多次调用。 – 2010-10-04 22:23:36

回答

2

您可以创建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); 
    } 
} 
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() 
    { 

    } 
} 
+0

我看到这个例子在做什么以及它的代码太多了:)但是,除非我错了不是Evs想要ConcreteComponentA.AnotherOperation调用ConcreteDecoratorA.Operation的原始问题,但它不能。这段代码仍然存在相同的问题 - ConcreteComponentA.AnotherOperation不会调用装饰器。 – 2010-10-08 08:56:46

3

创建一个委托(或一个事件,如果你想支持多种装饰),其允许装饰者手动“覆盖”操作方法。

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

当不再需要装饰器来防止内存泄漏时,使用一次性模式来确保事件被取消挂钩。

0

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

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

不错,但在我们的案例中,我们将从服务返回的内容转换为我们自己的域级POCO。如果我们的界面后面的系统被切换出来,我们需要添加ConcreteComponentB,然后我们必须重新创建CachingComponentA作为CachingComponentB。这就是封装很好的地方,无论我们有多少具体的实现,我们只有一个缓存类。 – 2010-10-06 22:50:11

2

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