Verhalten einer Anwendung per Konfiguration bzw. Laufzeit verändern
27.02.08 - Entwicklung, Diskussionen, Patterns, .NET, Grundlagen
Beitrag von Norbert Eder
Beitrag von Norbert Eder
AbgrenzungIn diesem Artikel wird folgendes gezeigt:
EinführungIn vielen Fällen ist es notwendig, Verhaltensweisen über die Konfiguration oder per Laufzeit zu steuern. Als Beispiel: Nehmen wir eine Anwendung, die unterschiedliche Listen zur Verfügung stellt. Nun müssen die Daten für diese Listen geladen werden, damit sie angezeigt werden können. Unter der Annahme, dass alle Daten aus einer Datenbank kommen ist es kaum notwendig, dieses Verhalten zu verändern. Nun kann es aber sein, dass Inhalte für bestimmte Listen nicht aus der Datenbank kommen, sondern aus anderen Quellen. Das Standardverhalten würde nun nicht mehr funktionieren und muss daher ausgetauscht werden. Ein weiterer häufig auftretender Punkt ist, dass zwar grundsätzlich das Standardverhalten verwendet werden soll, bis auf einen kleinen Teil, beispielsweise eine bestimmte Methode. Strategy Pattern hilft bei VerhaltensänderungenDa Verhaltensweisen getauscht werden können, liegt es nahe, sich im Bereich der Behavioral Patterns umzusehen. Darunter ist das Strategy-Pattern zu finden. Dieses ermöglicht, das gesamte Verhalten auszutauschen. Hier eine UML-Übersicht dieses Patterns:
Im Diagramm ist der Aufbau einfach zu erkennen. Grundsätzlich wird ein Interface IStrategy zur Verfügung gestellt. Dieses schreibt die Methode DoWork vor, welche dann die tatsächliche Aufgabe ausführt. Dieses Interface wird von zwei konkreten Klassen implementiert: ConcreteStrategy1 und ConcreteStrategy2. Beide Klassen besitzen also die Methode DoWork. Allerdings unterscheiden sich diese beiden Implementierungen voneinander, sprich das Verhalten ist ein unterschiedliches (sonst würde auch die Erstellung von zwei konkreten Klassen wenig sinnvoll sein). Schließlich gibt es noch einen StrategyContext. Dieser bekommt über den Konstruktor ein IStrategy übergeben. Dieses bestimmt nun das Verhalten. Die Aufrufe erfolgen über den StrategyContext, wodurch die Funktionalität aus dem übergebenen Strategy-Objekt aufgerufen wird. Eine Beispiel-Implementierung sieht so aus: public interface IStrategy { void DoWork(); } public class ConcreteStrategy1 : IStrategy { #region IStrategy Members public void DoWork() { Console.WriteLine("ConcreteStrategy1"); } #endregion } public class ConcreteStrategy2 : IStrategy { #region IStrategy Members public void DoWork() { Console.WriteLine("ConcreteStrategy2"); } #endregion } public class StrategyContext { private IStrategy _strategy; public StrategyContext(IStrategy strategy) { _strategy = strategy; } public void DoWork() { _strategy.DoWork(); } } Der Aufruf des Konstruktes geschieht folgendermaßen: class Program { static void Main(string[] args) { StrategyContext context = new StrategyContext(new ConcreteStrategy1()); context.DoWork(); } } Austausch von einzelnen MethodenBisher haben wir gesehen, wie gesamte Verhaltensweisen ausgetauscht werden können. Wie sieht es jedoch aus, wenn nur einzelne Methoden getauscht werden und das restliche Verhalten gleich bleiben soll? Hier bietet sich ein Structural Pattern an, das Proxy Pattern. Dieses Pattern beschreibt einen möglichen Weg, wie Aufrufe an ein Ziel weitergeleitet werden können. Dies ist genau das was wir benötigen. Zuerst jedoch das UML Diagramm, um einen ersten Überblick zu erlagen:
Wie zu sehen ist, wird der StrategyContext gegen einen Proxy ersetzt. Dieser erhielt zusätzlich zur eigentlichen DoWork-Methode eine weitere Überladung, dem ein ICommand übergeben werden kann. Damit kann entweder das Standardverhalten (welches durch die konkrete Strategy-Implementierung vorgegeben wird) oder aber ein beliebiges eigenes Verhalten ausgeführt werden. Dies wirft natürlich die Frage auf, warum man nicht trotzdem ausschließlich mit dem Strategy-Pattern arbeiten kann. Der Einfachheit wegen hat dieses Beispiel nur eine einzige Methode, diese Variante kommt jedoch erst bei mehreren angebotenen Methoden zu tragen. Hier eine Beispielimplementierung (IStrategy und die konkreten Implementierungen haben sich nicht geändert und sind im obigen Sourcecode zu finden): public interface ICommand { void Execute(); } public class CustomWorker1 : ICommand { #region ICommand Members public void Execute() { Console.WriteLine("CustomWorker1"); } #endregion } public class CustomWorker2 : ICommand { #region ICommand Members public void Execute() { Console.WriteLine("CustomWorker2"); } #endregion } public class StrategyProxy { private IStrategy _usedStrategy; public StrategyProxy(IStrategy usedStrategy) { _usedStrategy = usedStrategy; } public void DoWork() { _usedStrategy.DoWork(); } public void DoWork(ICommand customWorker) { customWorker.Execute(); } } FazitDieser Beitrag hat gezeigt, wie gesamte Verhalten innerhalb einer Anwendung einfach ausgetauscht werden können bzw. einen Weg aufgezeigt, wie dies auf Basis von Methoden realisiert werden kann. Wer dies nun über eine Konfigurationsdatei konfigurieren möchte, der kann sich beispielsweise einen Builder basteln, welcher die Konfiguration ausliest und die darin angegebenen Typen mit Hilfe der Klasse Activator instanziert, den Proxy mit den notwendigen Instanzen füllt und ihn anschließend fertig konfiguriert zurück liefert. | |
Thomas Darimont
27.02.08
| Hallo,
mit einem DynamicProxy: http://www.tutorials.de/forum/net-application-und-service-design/249443-dynamic-proxy-unter-net.html http://www.tutorials.de/forum/net-application-und-service-design/259066-getypte-datenmodelle-nur-mit-interfaces-und-dynamic-proxies.html spart man sich das Erstellen einer dedizierten Proxy Klasse. Man muss nur einen entsprechenden IInterceptor / InvocationHandler implementieren welcher den entsprechenden Methodenaufruf entweder zu einem eigentlichen Target durchschleift oder an eine Strategy delegiert. Ob man bestimmte Methodenaufrufe durchschleifen oder durch eine Strategy Anwendung ersetzen möchte, kann man dann beispielsweise über ein IDictionary<string,IStrategy> machen. Dabei sind die Key's die Methoden Signaturen der Methoden und die Values die entsprechenden Strategien. Gruß Tom | |
Norbert
27.02.08
| Guter Einwand und sicherlich in vielen Fällen sinnvoll. Nachteilig ist anzusehen, dass eine weitere Abhängigkeit (Spring.NET) hinzukommt. Dies bedeutet, dass folgende Problematiken entstehen:
- Zusätzliche Abhängigkeit als solches - Steigende Komplexität - Know-How-Aufbau notwendig Dies ist für kleinere Anwendungen meist nicht gewollt, für größere Anwendungen sehe ich es als durchaus sinnvoll an, diese "Investition", als auch die zusätzliche Abhängigkeit in Kauf zu nehmen. | |
Thomas Darimont
28.02.08
| Dynamic Proxies sind kein eigenes Konzept von Spring.NET (Werden dort jedoch intensiv benutzt).
Man kann auch mit dem reinen .Net Framework Out of the Box Dynamic Proxies erzeugen (RealProxy) oder solche gar selbst generieren via System.Reflection.Emit. Gruß Tom | |
Norbert
28.02.08
| Das ist schon klar, war auch im Bezug auf dein gegebenes Beispiel. Dennoch ändert sich für meine Aussage kaum etwas. | |
Kommentar hinzufügen
Bitte das Formular ausfüllen, um Deinen Kommentar hinzuzufügen.