2010-10-04 9 views
7

Wir verwenden derzeit das Decorator-Entwurfsmuster, um Caching durchzuführen. So haben wir eine Reihe von Klassen, die wie folgt aussehen:Ein Problem bei der Verwendung des Decorator-Entwurfsmusters

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

Also, wenn ein Client-Caching verwenden wollten, sie würden erstellen Sie eine neue ConcreteDecoratorA und passieren in einem ConcreteComponentA an den Konstruktor. Das Problem, vor dem wir stehen, ist, stellen Sie sich vor, dass AnotherOperation() einen Aufruf zur Operation benötigt, um seine Arbeit zu erledigen. ConcreteComponentA könnte nun wie folgt aussehen:

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

Das Problem, das ist, wenn Betrieb() Methode aus AnotherOperation() -Methode aufrufen, wird der Dekorateur Implementierung nie genannt werden, weil offensichtlich der Dekorateur nicht in der Vererbungshierarchie ist von ConcreteComponentA.

Haben wir irgendwo eine schlechte Designentscheidung getroffen oder ist dies nur eine Einschränkung des Dekorationsdesignmusters, das wir akzeptieren müssen?

Beachten Sie, dass in meinem realen Beispiel ConcreteComponentA ein Wrapper für ein Drittanbietersystem ist, über das wir keine Kontrolle haben. Wir haben IComponent und eine Reihe von POCOs entwickelt, mit denen wir dieses Drittanbietersystem abstrahieren. In diesem Fall müssen wir zwei Aufrufe an ihr System machen, um die benötigten Daten zu erhalten, es geht nur darum, wo wir diese beiden Anrufe tätigen.

+0

Haben Sie keine Kontrolle über ConcreteComponentA oder das Drittanbietersystem, das von ConcreteComponentA umschlossen wird? – dtb

+0

Ja, ConcreteComponentA ist eine unserer Klassen. Aufgrund von Politik, Budget usw. ist es schwierig und äußerst unwahrscheinlich, dass Dinge in einem Drittparteisystem geändert werden. Daher definiert IComponent eine einzelne Operation, die wir verwenden möchten, aber aufgrund der Struktur ihres Service müssen wir mehrere Aufrufe innerhalb dieser einzigen Operation in ConcreteComponentA durchführen. –

Antwort

2

Sie könnten eine Überladung von AnotherOperation erstellen, die die IComponent als Parameter verwendet.

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

Da Sie die Kontrolle über beide Ebenen (ConcreteComponentA und ConcreteDecoratorA), Sie können sie haben Hand Notizen hin und her:

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

Ich sehe, was dieses Beispiel tut, und es ist ein schönes Stück Code :) Aber .. wenn ich nicht falsch bin, ist nicht das ursprüngliche Problem, das Evs ConcreteComponentA.AnotherOperation durch ConcreteDecoratorA.Operation aufrufen möchte, aber es kann nicht. Dieser Code hat immer noch das gleiche Problem, nicht - ConcreteComponentA.AnotherOperation ruft nicht zum Decorator auf. –

3

einen Delegaten erstellen (oder ein Ereignis, wenn Sie mehrere Dekorateure unterstützen wollen), dass ermöglicht Dekoratoren, die Operation-Methode manuell zu "überschreiben".

Versuchen Sie im Decorator-Konstruktor, die Komponenteninstanz in Ihren konkreten Komponententyp zu konvertieren und einen Operation-Override-Delegaten anzuhängen.

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

die Einweg-Muster verwenden, um sicherzustellen, dass die Veranstaltung ausgehängt wird, wenn der Dekorateur nicht mehr benötigt Speicherlecks zu verhindern.

0

Ich ziehe Vererbung zu verwenden, anstatt Verkapselung mein Caching zu tun, auf diese Weise wird die im Cache gespeicherten Wert der Methode Caching verwenden, weil es virtuell ist:

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

Dann, wenn Sie einen Dekorateur verwenden Objekt, this.Operation() wird die Decorator-Klasse verwenden.

+0

Ja, das ist fair, aber in unserem Fall konvertieren wir das, was vom Dienst zurückgegeben wird, in unsere eigenen POCOs auf Domänenebene. Wenn das System hinter unserer Schnittstelle ausgewechselt werden müsste, müssten wir ConcreteComponentB hinzufügen, und dann müssten wir CachingComponentA als CachingComponentB neu erstellen. Das ist, wo Kapselung ist nett, wir haben nur eine einzelne Caching-Klasse, egal wie viele konkrete Implementierungen wir haben. –

2

Selbstaufrufe sind die Begrenzung der Dekorationsmuster, das stimmt. Die einzige Möglichkeit, Basiskomponenten-Selbstaufrufe abzufangen, ohne sie ändern oder zusätzliche Infrastruktur hinzufügen zu müssen, ist die Vererbung. Also, wenn Sie nicht mögen Lösungen von oben und Sie wollen immer noch die Flexibilität, die Decorator gibt Ihnen (Möglichkeit, eine beliebige Anzahl und eine Reihenfolge der Dekoratoren), können Sie nach einer Implementierung von dynamischen Proxy suchen, die Subtypen generiert (dh Unity Interception, Castle Dynamic Proxy).

Verwandte Themen