2012-04-04 25 views
7

Haftungsausschluss: Ich würde gerne dependency injection auf diesem Projekt mit sein und eine lose gekoppelte Interface-basierte Design auf der ganzen Linie, aber die Verwendung von Abhängigkeitsinjektion wurde in diesem Projekt abgeschossen. Auch SOLID Design-Prinzipien (und design patterns im Allgemeinen) sind etwas Fremdes, wo ich arbeite und ich bin neu für viele von ihnen selbst. Also nehmen Sie das in Erwägung, wenn Sie ein besseres Design für dieses Problem vorschlagen.Gibt es eine bessere Designoption?

Hier ist eine vereinfachte Version des Codes, an dem ich arbeite, und als solcher könnte es erfunden scheinen. Wenn das so ist, entschuldige ich mich. Betrachten Sie die folgenden Klassen:

// Foo is a class that wraps underlying functionality from another 
// assembly to create a simplified API. Think of this as a service layer class, 
// a facade-like wrapper. It contains a helper class that is specific to 
// foo. Other AbstractFoo implementations have their own helpers. 

public class Foo : AbstractFoo 
{ 
    private readonly DefaultHelper helper; 
    public override DefaultHelper Helper { get { return helper; } } 

    public Foo() 
    { 
     helper = new Helper("custom stuff"); 
    } 

    public override void Operation1(string value) 
    { 
     Console.WriteLine("Operation1 using " + value); 
    } 

    public override void Operation2() 
    { 
     Console.WriteLine("Operation2"); 
    } 
} 

// Helper derives from a default implementation and allows us to 
// override it's methods to do things specific for the class that 
// holds this helper. Sometimes we use a custom helper, sometimes 
// we use the default one. 

public class Helper : DefaultHelper 
{ 
    private readonly string customStuff; 

    public Helper(string value) 
    { 
     customStuff = value; 
    } 

    public override void DoSomethingHelpful() 
    { 
     Console.WriteLine("I was helpful using " + customStuff); 
    } 
} 

sagen, diese zwei Klassen werden wie folgt verwendet:

// foo referenced and used in one part of code 
    var foo = new Foo(); 
    foo.Operation2(); // or foo.Operation1(); 

    // some other point in the program where we don't have a reference to foo 
    // but do have a reference to the helper 
    helper.DoSomethingHelpful(); 

jedoch nun heraus, dass ich finde, dass ich muß auch ausführen foo.Operation1 in einige Implementierungen von helper.DoSomethingHelpful();? Mögliche Umgehungslösungen, an die ich dachte, wären:

  1. Haben foo und Helfer eine bidirektionale Beziehung. So dass in DoSomethingHelpful können wir foo.Operation2
  2. rufen Haben foo iHelp-Schnittstelle implementieren und die „Helfer“ Code in foo
  3. Verwenden Delegation bewegen und die Methode Operation2 als Action<string> Delegierter in den Konstruktor der Helfer übergeben.

Keiner dieser Ansätze scheinen ideal zu sein (obwohl ich ziemlich festgestellt haben, ich weiß nicht wie Option 1 und mache mir Sorgen um die Wartbarkeit mit Option 3 wenn wir später erfahren wir bestehen müssen in mehr Delegierten). Das lässt mich fragen, ob es ein Problem mit dem ursprünglichen Design der Helper/Foo Combo gibt. Gedanken?

+0

Ich denke, du meinst 'DefaultHelper' statt' DefualtHelper'. – ja72

+0

@ ja72, Rechtschreibung war nie mein starker Anzug, ich habe diesen Tippfehler aktualisiert. – Matt

+1

+1 für den Haftungsausschluss ... Hatte ein großes Lachen. – scottheckel

Antwort

1

„Keiner dieser Ansätze scheinen ideal zu sein (obwohl ich ziemlich viel habe Ich mag keine Option 1 bestimmt und mache mir Sorgen um die Wartbarkeit mit Option 3, wenn wir später erfahren wir in geben müssen mehr Delegierte). Das lässt mich fragen, ob es ein Problem mit dem ursprünglichen Entwurf von der Helfer/Foo-Kombination gibt. "

Sie sind genau richtig - es gibt ein Problem mit dem Design von Helper und Foo. Die grundlegende Foo/Helper-Beziehung, wie Sie sie ursprünglich beschrieben haben, ist in Ordnung und ein allgemeines Muster, wenn Sie andere Objekte umbrechen müssen, die Sie nicht kontrollieren. Aber dann sagen Sie:

„Was ist, wenn ich herausfinden, dass ich auch foo.Operation1 in einigen Implementierungen von helper.DoSomethingHelpful() durchführen ;?“

Hier haben wir ein Problem. Du hast angefangen, eine Beziehung zu beschreiben, in der Foo von Helfer abhängt; Jetzt beschreiben Sie eine Beziehung, in der Helper von Foo abhängig ist. Das sagt mir sofort, dass deine Abhängigkeitsbeziehungen verwickelt sind. Abhängigkeitsbeziehungen zwischen Objekten sollten nur in eine Richtung gehen; tatsächlich beruht die Abhängigkeitsinjektion darauf.

+0

+1 Ich stimme hier zu und danke Ihnen für die Identifizierung, warum ich Option 1 und 3 nicht mochte (d. H. Verschlungene Abhängigkeiten). Und warum ich denke, dass die Kombination von Foo und Helper in diesem Fall aufgrund ihrer gegenseitigen Abhängigkeit sinnvoll ist. Mithilfe der IHelp-Schnittstelle kann ich Foo weiterhin so behandeln, als wäre es ein Helfer an einer anderen Stelle im Code. – Matt

1

Ich denke, Sie haben, was Sie brauchen. Versuchen Sie, nicht für das "nur für den Fall, dass ich es später brauche" zu entwerfen und nicht zu reparieren, was nicht kaputt ist. Wenn Sie in der Zukunft Operation1 von Ihrem Helper verwenden müssen, fügen Sie sie als eine Abhängigkeit vom Konstruktor hinzu (wie Sie es vorgeschlagen haben), oder übergeben Sie sie einfach an die Methode, die Sie aufrufen. Es hängt vom Szenario ab, und Sie werden es haben, wenn Sie tatsächlich etwas brauchen.

EDIT: änderte die "Versuchen Sie, nicht für die Zukunft zu entwerfen", wie es scheint nicht, was ich sagen möchte.

EDIT erneut wegen Änderungen in der Frage

Sie könnten so etwas wie dieses:

helper.DoSomethingUsefulWith(foo); 

so Ihre Hilfsmethode wird die Abhängigkeit es braucht, um erhalten

+0

Versuchen Sie, nicht für die Zukunft zu entwerfen? Ich verstehe, was Sie sagen wollten, aber im Allgemeinen halte ich das nicht für einen guten Rat. –

+0

Entwerfen Sie es so, dass es gepflegt und erweitert werden kann: JA !!! Hinzufügen von Funktionen für den Fall, dass es später benötigt wird: NEIN !!! das versuche ich zu sagen :). Ich denke, ich habe nicht die besten Worte dafür gewählt hehe – ivowiblo

+0

@ivowiblo, ich habe meine Frage aktualisiert, weil es nicht wirklich eine "Was wäre wenn" -Situation wäre. Dies ist eine Anforderung, auf die ich jetzt gestoßen bin. Ich hätte klarer sein sollen. Es tut uns leid. – Matt

3

arbeiten Wie über eine Gelegenheitsbeziehung ("verwendet"):

public class Helper : DefaultHelper 
{ 
    private readonly string customStuff; 

    public Helper(string value) 
    { 
     customStuff = value; 
    } 

    public override void DoSomethingHelpful(AbstractFoo foo) 
    { 
     foo.Operation1(); 
     Console.WriteLine("I was helpful using " + customStuff);   
    } 
} 

So ändern Sie den abstrakten Helper, um einen Verweis auf die richtige Foo Implementierung zu erwarten.

+1

In UML ist dies die [Abhängigkeit] (http://publib.boulder.ibm.com/infocenter/rtnlhelp/v6r0m0/index.jsp?topic=%2Fcom.ibm.xtools.modeler.doc%2Ftopics%2Fcdepend. html) oder "uses" -Verbindung – SwDevMan81

+0

Das Problem dabei ist, dass Helper die gleichen Signaturen hat, die von AbstractFoo und anderen Klassen verwendet werden (zB AbstractBoo). Wenn ich die Basisklasse so ändere, dass sie foo akzeptiert, dann ist das nicht hilfreich, wenn ich einen Helfer von boo verwende, wenn das sinnvoll ist. Boo muss möglicherweise stattdessen übergeben werden (oder es könnte in Ordnung sein, ohne Parameter zu sein). – Matt

+0

@Matt: Willst du, dass 'BooHelper' Zugriff auf' Boo' hat, so wie du 'FooHelper' Zugriff auf' Foo' hast, oder nicht? Deine Bemerkung hat mich wirklich verwirrt. – ja72

1

Ich denke, dass alle Ihre Lösungen gut sind; Sie bieten nur unterschiedliche Fähigkeiten. Diese Unterschiede spielen jetzt keine große Rolle mehr, werden aber in Zukunft wahrscheinlich sein.

Sie bevorzugen die zweite, und Ihre Instinkte sind die beste Anleitung hier, Sie wissen mehr als der Rest von uns über die Zukunft Ihres Codes. Ich mag deine zweite Lösung am besten, nur weil sie eine Klasse los wird und einfacher ist. Aufgrund seiner Einfachheit müssen Sie, wenn Sie später etwas anderes tun müssen, nicht viel Arbeit wegwerfen.

Mit der ersten Methode können Sie Spiele mit verschiedenen Helper (IHelper?) - Instanzen und Unterklassen spielen. Die letzte Methode fügt Helper viel Flexibilität hinzu. (Obwohl es so viel hinzufügen kann, benötigen Sie keinen Helfer, sondern nur die Methode, die Sie ihm übergeben.) Sie können später zu einem späteren Zeitpunkt wechseln, wenn Sie mehr ungelöste Probleme der Zukunft zu lösen scheinen.

+0

+1 Ich stimme dem Interface-Ansatz zu und die Kombination von Foo und Helper ist wahrscheinlich der bessere der drei und ist der, mit dem ich mich entschieden habe. – Matt

Verwandte Themen