2016-04-01 4 views
1

Ich habe eine Situation, wo ich eine IEnumerable habe, die ich durchlaufen und Code für jedes Element ausführen muss.polymorphe Erweiterung zu 3rd-Party-Klassen

Dieser auszuführende Code hängt vom tatsächlichen Typ des Elements ab, und was ich suche, ist eine gute, saubere Methode, dies ohne irgendwelche Bedingungen zu tun. Wenn also die Anzahl der abgeleiteten Typen zunimmt, nehme ich zu Sie müssen einfach einen neuen Handler schreiben und meinen vorhandenen Code nicht ändern.

Um dies zu veranschaulichen ich das Beispiel, wo die 3rd-Party-Bibliothek enthält folgende Code:

public abstract class BaseObject{ } 

public class Derived1: BaseObject { } 

public class Derived2 : BaseObject { } 

und meinem lokalen Code tut so etwas wie:

void Execute(IEnumerable<BaseObject> list) 
{ 
    foreach (var item in list) 
     item.DoWork(); 
} 

ich versucht habe Schaffung Erweiterungsmethoden, um dies zu tun:

public static class Extensions 
{ 
    public static int DoWork(this BaseObject obj) { return 0; } 

    public static int DoWork(this Derived1 obj) { return 1; } 

    public static int DoWork(this Derived2 obj) { return 2; } 
} 

die offensichtlich nicht funktioniert, da jedes Element in der Enumeration vom Typ BaseObject ist, so wird immer die Erweiterungsmethode aufgerufen, die 0 zurückgibt.

habe ich die Möglichkeit, einen neuen abgeleiteten Typ für jeden abgeleiteten Typ alle Implementierung einer Schnittstelle zu schaffen:

public class MyDerived1: Derived1, IMyInterface 
{ 
    public int DoWork(){return 1;} 
} 

public class MyDerived2: Derived2, IMyInterface 
{ 
    public int DoWork(){return 2;} 
} 

public interface IMyInterface 
{ 
    int DoWork(); 
} 

dann konnte ich durchlaufen und gegossen in die neue Schnittstelle:

void Execute(IEnumerable<BaseObject> list) 
{ 
    foreach (var item in list) 
    { 
     var local = item as IMyInterface; 
     local?.DoWork(); 
    } 
} 

, aber dies erfordert die Konstruktion meines abgeleiteten Typs, und die Entscheidung, welcher Typ zu konstruieren ist, erfordert immer noch eine bedingte Anweisung, die für jeden geändert werden müsste neuer Typ hinzugefügt.

Verwenden Sie eindeutig das Fabrikmuster hier, und es ist die sauberste Lösung, die ich bisher habe.

Das Befehlsmuster bietet eine Lösung, sondern als Befehl von der Art des abgeleiteten Typs abhängig auszuführen ist, noch ein bedingter diesen Befehl zu konstruieren ist erforderlich ...

jemand einen Ansatz vorschlagen kann, das wäre Vollständig eliminieren Sie alle Bedingungen, die geändert werden müssten, wenn neue abgeleitete Typen zum Mix hinzugefügt werden?

+0

Sie könnten ein Wörterbuch beim Start aufbauen, mithilfe von Reflektion, die Ihre Fabrik die richtige Art, ohne Notwendigkeit irgendwelcher conditionals abgeleitet erlauben würde, zu konstruieren und die Karte würde automatisch alle neuen abgeleiteten Typen, die, ohne Modifikation hinzugefügt wurden . – Baldrick

+0

danke @Baldrick, das ist der Ansatz in der Antwort in diesem Beitrag http://stackoverflow.com/questions/27724130/simulate-inheritancity-by-extension genommen. Um ehrlich zu sein, wollte ich nicht nachdenken. – MikeW

+0

Ich bin mir nicht sicher, ob das möglich ist. Die erforderlichen Informationen über den zugrunde liegenden Typ eines BaseObject können nicht programmatisch aufgedeckt werden, ohne dass (a) eine virtuelle Methode das Objekt selbst für eine überschriebene Methode aufruft (es ist jedoch ein Drittanbieter und kann nicht geändert werden), oder (b) Reflektieren, dann Konditional verwenden. Nicht sicher, es gibt einen anderen Weg in deiner Situation. – Baldrick

Antwort

0

Eine mögliche Lösung verwendet eine Zusammensetzung Framework, ich habe MEF für mein Beispiel gewählt.

Was Sie tun müssen, ist Handler zu erstellen, die eine gemeinsame Schnittstelle implementieren, die jeweils in der Zusammensetzung Behälter mit einem Vertragsnamen enthalten ist, den Namen des abgeleiteten Typ übereinstimmt, gefunden

public interface IBaseObjectWorker 
{ 
    int DoWork(BaseObject obj); 
} 

[Export(contractType: typeof(IBaseObjectWorker), contractName: "UnitTestProject1.Derived1")] 
public class DerivedObject1Worker : IBaseObjectWorker 
{ 
    public int DoWork(BaseObject obj) 
    { 
     return 1; 
    } 
} 

[Export(contractType: typeof(IBaseObjectWorker), contractName: "UnitTestProject1.Derived2")] 
public class DerivedObject2Worker : IBaseObjectWorker 
{ 
    public int DoWork(BaseObject obj) 
    { 
     return 2; 
    } 
} 

dann können Sie die bekommen Handler aus dem Behälter Zusammensetzung und Verwendung, dass:

void Execute(IEnumerable<BaseObject> list) 
{ 
    foreach (var item in list) 
    { 
     var worker = DIContainer.Instance.GetObject<IBaseObjectWorker>(item.GetType().ToString()); 
     worker?.DoWork(item); 
    } 
} 

so diese verlängern ein neuer abgeleiteter Typ würde einfache Mittelwert Erstellung eines neuen Handlers Typ zu handhaben.Wenn der Zusammensetzungscontainer mit allen Typen aus der Assembly gefüllt wird, wäre der neue Handler gleich nach seiner Erstellung vorhanden und würde mit der Verarbeitung des neuen abgeleiteten Typs beginnen, ohne den aktuellen Code zu ändern. So gehorcht das Offen-Geschlossen-Prinzip.

Dies ist eine Art einer Implementierung des Fabrikmusters, aber ohne sich um die Implementierung der Fabrik kümmern zu müssen.

als eine Seite nicht der DIContainer ist meine persönliche Implementierung eines einzelnen Muster-Wrappers um einen MEF-Container, diese wird beim Start der App gefüllt und der GetObject (String-Name) ruft einfach den GetExportedValue (string contractName).

0

Sie könnten die natürlichen Merkmale des Polymorphismus verwenden.

class Class1 
{ 
    public virtual void DoThis() 
    { 
     Console.WriteLine("Class one"); 
    } 
} 

class Class2 : Class1 
{ 
    public override void DoThis() 
    { 
     Console.WriteLine("Class two"); 
    } 
} 

class Class3 : Class1 // or Class2 
{ 
    public override void DoThis() 
    { 
     Console.WriteLine("Class three"); 
    } 
} 


    private static void Main(string[] args) 
    { 
     Class1 class1 = new Class1(); 
     Class2 class2 = new Class2(); 
     Class3 class3 = new Class3(); 
     class1.DoThis(); 
     class2.DoThis(); 
     class3.DoThis(); 
    } 

Und so weiter.

private static void Main(string[] args) 
{ 
    Class1 class1 = new Class1(); 
    Class2 class2 = new Class2(); 
    Class3 class3 = new Class3(); 
    class1.DoThis(); 
    class2.DoThis(); 
    class3.DoThis(); 
} 

Daher würde es braucht keine Behandlung zu sein, was Objekttyp es ist, wäre es natürlich auf die überschriebene Methode der Klasse fallen.

Die aktuelle Route, die Sie nehmen, benötigt eine Untersuchung des Objekttyps, die unordentlich wird, da mehr abgeleitete Klassen hinzugefügt werden und höchstwahrscheinlich eine bedingte Anweisung irgendwo im Code benötigen, um zu bestimmen, wohin Ihr Programm fließen soll . Wenn Sie für jede abgeleitete Klasse eine überschriebene Methode verwenden, wird Ihr Programm automatisch dorthin gebracht, wo Sie es haben möchten.

Um meine Art zu denken, ist es eine unnötige Steigerung der Arbeitsbelastung durch den Versuch, Facetten der Sprache zu wiederholen, die OOP relevant sind. Manchmal kann ein einfacherer Ansatz besser und eleganter sein.

+0

Sie gehen hier davon aus, dass der '3rd Party Library Code' geändert werden kann. – Maarten

+0

@Maarten ah ja ich bin. Ich mochte deine dynamische Lösung. Ich denke, es ist einfacher als ein Wörterbuch, wie in den Kommentaren erwähnt wurde. –

4

Sie könnten dynamic verwenden. Normalerweise bin ich kein Fan davon, aber in diesem Fall scheint es eine gute Option zu sein.

public static class Worker 
{ 
    public static int DoWork(BaseObject obj) { return 0; } 
    public static int DoWork(Derived1 obj) { return 1; } 
    public static int DoWork(Derived2 obj) { return 2; } 
} 

void Execute(IEnumerable<BaseObject> list) { 
    foreach (dynamic item in list) { 
     Worker.DoWork(item); // Method resolution done at run-time 
    } 
} 
+1

Effektiv immer noch "Reflexion", aber zumindest ist es versteckt ohne eine Last von hässlichem Code benötigt. +1 von mir. – Baldrick

+0

Ich denke, wenn das OP die Klassen nicht ändern kann, ist dies eine gute Lösung +1 –