Freitag, 29. August 2014

Mit Strategie zum Erfolg.

Entwurfsmuster für Algorithmen

Artikelübersicht
1. Teil Folgt der Algorithmus einem Muster?
2. Teil Mit Strategie zum Erfolg.


Manchmal gibt es viele verschiedene Algorithmen, die am Ende das Gleiche erledigen, nur eben auf unterschiedliche Art und Weise. Es kommt auf die Umstände an, welchen dieser Algorithmen Sie benutzen wollen oder müssen. Sie streben ein Ziel an, berücksichtigen die Mittel und Umstände, dann entscheiden Sie sich und wählen eine Strategie. Auch unter den Entwurfsmustern gibt es eines, welches Ordnung in eine Familie von Algorithmen bringt. Die Gang of Four definiert den Zweck des sogenannten Strategy Pattern wie folgt: "Definiere eine Familie von Algorithmen, kapsele jeden einzelnen und mache sie austauschbar. Das Strategiemuster ermöglicht es, den Algorithmus unabhängig von ihn nutzenden Klienten zu variieren." [S.373, GOF11]




Man trennt also die Algorithmen vom Kontext, in dem sie zur Anwendung kommen. Der Kontext greift auf eine Familie von möglichen Strategien zu, die der Klient dann auswählt. Die Strategie ist eine abstrakte Klasse oder ein Interface. Diese definiert eine Methode für den entsprechenden Algorithmus (algorithm). Von dieser abstrakten Klasse werden die konkreten Strategien abgeleitet (StrategyA, StrategyB, StrategyC). Die konkreten Strategien sind instanziierbare Klassen, die die konkreten Algorithmen implementieren. Der Kontext besitzt eine Referenz auf die allgemeine Strategie. Der Klient injiziert dem Kontext seine Wahl für die konkrete Strategie.

Im Folgenden wird das Ganze an einem Beispiel anschaulich gemacht. Der Klient ist ein Mitglied unseres Requirements-Gremiums, welches Artefakte priorisiert. Um das Artefakt zu priorisieren stehen uns eine Vielzahl von Priorisierungsmethoden zur Verfügung. Ganz nach den Umständen wählen wir die eine oder die andere Methode zum Priorisieren aus. In Java schreiben wir eine abstrakte Klasse Prioritization. Diese Klasse definiert die Methode prioritize. Von der abstrakten Klasse Prioritization leiten wir zwei Klassen ab, die Klasse SimplePrioritization und DifficultPrioritization. Diese beiden Klassen implementieren die konkreten Priorisierungsmethoden, eine leichte und eine schwere Methode.



Je nachdem, wie gut geschult unser Personal ist, können wir uns nun für eine leichte oder für eine schwere Priorisierungsmethode entscheiden. Das geschieht für ein Artefakt. Wir schreiben die Klasse Artifact. Innerhalb dieser Klasse setzen wir eine Referenz auf die abstrakte Klasse. Für das so gebildete Attribut prioritization generieren wir einen Setter. Über den Setter können wir den entsprechenden Algorithmus injizieren.

Welcher Algorithmus Verwendung findet, entscheidet das Gremiumsmitglied. Dafür erschaffen wir die Klasse Client. In dieser deuten wir innerhalb der Mainmethode die zu treffende Entscheidung an. Dieses Mitglied entscheidet sich für die schwierige Priorisierung, weil es sich für eine gut ausgebildete Fachkraft hält. Es ruft die Methode prioritize der Klasse Artifact auf, welche die entsprechende Strategie ausgeführt.

public class Client {

 public static void main(String[] args) {
  Artifact artifact = new Artifact();
  artifact.setPrioritization(new DifficultPrioritization());
  artifact.prioritize();
 }

}

public class Artifact {
 private Prioritization prioritization;

 public void setPrioritization(Prioritization prioritization) {
  this.prioritization = prioritization;
 }

 public void prioritize() {
  if (prioritization != null) {
   prioritization.prioritize();
  }
 }
}

public abstract class Prioritization {
 protected abstract void prioritize();
}

public class SimplePrioritization extends Prioritization {

 protected void prioritize() {
  System.out.println("Einfach priorisieren.");
 }

}

public class DifficultPrioritization extends Prioritization {

 protected void prioritize() {
  System.out.println("Schwer priorisieren.");
 }

}



Die Anwendung dieses Entwurfmusters gestattet die Bildung von Familien von Algorithmen, aus denen der Klient auswählen kann. Wir können später weitere Algorithmen zur Familie hinzufügen, ohne am Kontext etwas ändern zu müssen. Im letzten Post wurden von einer abstrakten Klasse Unterklassen abgeleitet, in denen sich unterschiedliche Algorithmen befanden. Diese Unterklassenbildung wird für den Kontext vermieden. Damit der Klient jedoch den richtigen Algorithmus injizieren kann, benötigt er Kenntnisse über die unterschiedlichen Algorithmen. Der Klient muss zwischen Kontext und Strategie vermitteln. In unserem Beispiel wählt der Klient die Klasse DifficultPrioritization, instanziiert ein Objekt davon und injiziert es dem Artefakt. Leicht ersichtlich ist, gegenüber der Implementierung der Algorithmen im Kontext, dass es eine starke Zunahme an Klassen gibt. Damit steigt natürlich die Komplexität unseres Codes. Zwischen den aufgezählten Vor- und Nachteilen gilt es im konkreten Fall abzuwägen.

Alan Shalloway oder James R. Trott geben uns in ihrem Buch "Entwurfsmuster verstehen" den folgenden Tipp, die wachsende Komplexität im Griff zu behalten. "Der einzig schwerwiegende Nachteil, den ich bei der Verwendung des Strategiemusters gefunden habe, ist die Anzahl der zusätzlichen Klassen, die man erzeugen muss. Auch wenn der Aufwand lohnt, habe ich dennoch einige Methoden entwickelt, die dies verhindern, wenn ich die volle Kontrolle über alle Strategien habe. Wenn ich in solch einer Situation mit C++ arbeite, dann sorge ich dafür, dass die Header-Datei der abstrakten Strategie-Klasse die Header-Dateien aller konkreten Strategie-Klassen enthält. Wenn ich in Java programmiere, dann verwende ich eingebettete Klassen (inner classes) für die konkreten Strategien, die sich innerhalb der abstrakten Strategie-Klasse befinden." [S.233, SHALL03] Diesen Tipp wollte ich Ihnen nicht vorenthalten.

Beschäftigen wir uns jetzt mit der Verbindung der Gedanken des letzten Posts mit dem Heutigen. Im letzten Post haben wir eine Schablone für einen Gesamtprozess geschaffen. Auch in diesem Prozess wurde die Methode prioritize verwendet. Je nachdem, ob wir uns für TeamA oder für TeamB entschieden haben, konnten wir auch hier verschiedene Methoden verwenden. Nun können wir beide Muster zusammenfügen. Wir ändern die Methode requirementsProcess so ab, dass wir ein Artifact-Objekt als Parameter übergeben. Die entsprechenden Methoden, wie z.B. prioritize, finden wir nun im Kontext. Der Kontext ruft nun wiederum die entsprechend strategisch gewählte Methode auf.

public class RequirementsProcessTemplate {

 public final void requirementsProcess(Artifact artifact) {
  artifact.investigate();
  artifact.document();
  artifact.match();
  artifact.prioritize();
 }

}

public class Client {

 public static void main(String[] args) {
  Artifact artifact = new Artifact();
  artifact.setPrioritization(new DifficultPrioritization());
  RequirementsProcessTemp rpt = new RequirementsProcessTemp();
  rpt.requirementsProcess(artifact);
 }

}

public class Artifact {
 …
 public void investigate() { }
 public void document() { }
 public void match() { }
}



Bei der Suche nach weiteren Entwurfsmustern, mit denen man Prozessabläufe und darin verwendete Algorithmen flexibler gestalten kann, würde ich mich sehr über Ihre Hilfe sehr freuen. Welche Design Pattern verwenden Sie? Wie benutzen Sie diese? Die untersuchten Muster werde ich dann in meiner Reihe zur Umsetzung eines Requirements Engineering verwenden.

Wichtiges zum Blog

Zum Schluss dieses Artikels möchte ich ein paar Änderungen in Bezug auf meinen Blog erwähnen. Die größte Änderung wird sein, dass ich nur noch alle zwei Wochen einen Post veröffentlichen möchte (SORRY!). Die gewonnene Zeit möchte ich in Studien, Reviews und Nachdenken stecken, damit ich die Qualität meiner Posts steigere. Natürlich werde ich immer noch mein Wissen, wie auch mein Unwissen darstellen, um dann von Ihren Erfahrungen zu lernen. Die nächsten Artikel werden also am 12.9., 26.9., 10.10, … erscheinen. Nicht ändern wird sich der Freitag als Veröffentlichungstermin, auch nicht die inhaltlichen Themen. Es wird weiterhin um systematische Softwareentwicklung in einer menschlichen Umgebung gehen.

Ich hoffe, Sie haben Verständnis dafür, dass ich psychologische und zwischenmenschliche Themen in die Gesamtbetrachtung miteinbeziehe. Wie beim Thema der Hochsensibilität werde ich mich auf wissenschaftliche und universitäre Forschungen beziehen und sie gepaart mit meinen Erfahrungen zur Diskussion stellen. In keinem Fall geht es mir um die Vermittlung von nicht ergründbarem Geheimwissen. Ich glaube, dass die größten Probleme in der Softwareentwicklung keine technischen Probleme sind, sondern zwischenmenschliche. Ich würde mich freuen, wenn Sie mich auf meinem Gedankenweg weiterhin begleiten würden.

  • [GOF11]: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: "Entwurfsmuster", deutsche Übersetzung, Addison-Wesley Verlag, Münschen, 2011
  • [SHALL03]: Alan Shalloway, James R. Trott: "Entwurfsmuster verstehen", 1.Auflage, mitp-Verlag, Bonn, 2003


vorheriger Post dieses Themas


Print Friendly Version of this page Print Get a PDF version of this webpage PDF

1 Kommentar:

  1. Es ist auch meine Erfahrung in der Praxis, dass die grössten Herausforderungen in Software-Projekten im zwischenmenschlichen Bereich liegen. Dennoch bin ich sicher, dass Informatik-Wissen eine zwingende Voraussetzung ist für das erfolgreiche Management von Softwareprojekten. Nicht jedes beliebige Projektmanagementschema führt hier zum Erfolg. Dies ist ein häufiges Missverständnis for allem bei Grossprojekten. Ich nenne das gerne "Kästchenmalen" ;-)

    AntwortenLöschen