Dieses Blog durchsuchen

Sonntag, 11. Juli 2010

Ansichten ändern sich!

Es ist nun schon eine Weile her, als ich hier einen Beitrag geschrieben hatte. Ich nutzte die Zeit, um mit der MPT ein wenig Erfahrung zu sammeln. Dies tat ich mit einem Projekt auf Arbeit.

Naja, es funktionierte schon alles so, wie ich mir das gedacht hatte. Jedes Objekt war im Grunde unabhängig voneinander und der Datenfluss geschah eher passiv. Eigentlich genau das, was ich wollte, oder?
Nun ja, nicht ganz.
So bereitete mir z.B. die Idee des Transmitters in einem Anwendungsfall ziemliche Probleme:
Nehmen wir an Objekt A ist ein Transmitter für irgendeinen Wert X.
Objekt B und Objekt C empfangen diesen Wert X.
Objekt B fordert den Wert X an. Diesen Wert bekommt Objekt B von Objekt A. Aber auch Objekt C bekommt diesen Wert X, weil es ja ebenfalls als Empfänger für diesen Wert registriert ist.

Das scheint soweit OK zu sein, oder? In meinem Fall war dies aber nicht gewollt, weil das unaufgeforderte Empfangen des Wertes X eine Ereigniskette ausgelöst hat, die nicht hätte ausgelöst werden dürfen.
Also, wenn Objekt A den Wert X anfordert, darf ihn nicht Objekt B bekommen. Und wenn Objekt B den Wert X anfordert, darf ihn nicht Objekt A bekommen.
Das ist ein ziemliches Dilemma, welches einen schon mal verzweifeln lässt.
Meine erste Idee war es ja, bei den Objekten A und B eine Flag-Variable einzuführen, welche in der Empfangsroutine prüft, ob der Wert X überhaupt angefordert wurde.
Doch das schlug ich mir schnell wieder aus den Kopf, denn so eine "Prüfung" verschmutzt die Businesslogik. Zudem wissen die Objekte gar nicht, was um sie herum geschieht. Diese Flag-Variable würde aber indirekt andeuten, dass dieses Objekt etwas über seine Umgebung weiß. Und das wäre schlecht!

Ich überlegte lange, wie ich das Problem lösen könnte. Dann kam mir die Idee! Man könnte ja einfach ein Objekt dazwischen schalten, welches wie ein Ventil funktioniert. Man schaltet es z.B. einfach zwischen Objekt A und Objekt B. Die Anforderung von Objekt B geht durch das "Ventil". Dabei wird in dem "Ventil" ein Flag gesetzt, welches den Empfang von Wert X erlaubt. Es ist somit "offen". Wenn dann der Wert kommt, wird der Wert an Objekt B durchgereicht und das Ventil wird wieder geschlossen. Bei einem unaufgeforderten Empfang wird der Wert einfach abgewiesen und Objekt B bekommt davon gar nix mit.

Nachdem ich das so eingebaut hatte, funktionierte alles wieder problemlos. Die Ventile sorgten für den gewünschten Datenfluss und die Businesslogik hatte sich absolut nicht geändert. Genial!

Unzufrieden, trotz entkoppelter Objekte
Die Anwendung funktionierte, aber mir war unwohl bei dem Gedanke, dass noch mehr Funktionen in die Software fließen sollten.
Wie kann das sein? Die Entkopplung sollte ja eigentlich das Gegenteil bewirken und Sicherheit bei der Implementation neuer Funktionen erzeugen. Ein Grund für die Unsicherheit war wohl auch das oben geschilderte Szenario.
Viel schlimmer war aber gerade die totale Isolierung der einzelnen Businessobjekte zueinander. Das heißt, ich konnte aus einem Objekt heraus nicht die Kommunikationsstrecke verfolgen.
Wollte ich also wissen, was passiert, wenn ich auf einen bestimmten Button klicke, so kam ich zwar zur entsprechenden Aufrufmethode, doch dann war da eine Sackgasse. Der Isolation sei "Dank", stand da in den jeweiligen Methoden nur die Floskel "Say.Carefully(MacheIrgendwasSinnvolles, this)".
"Ach ja, toll! Und welches Objekt ist jetzt nochmal dafür zuständig, diese Sache auszuführen?" fragte ich mich des öfteren.
Denn leider steht man hier wirklich in der Sackgasse. Man kann nicht einfach zur Definition von "MacheIrgendwasSinnvolles" springen, um den Programmfluss nachzuvollziehen. Das ist der Preis, den man für die Isolation bezahlt. Und der ist teuer!
Um also zu wissen, welches Objekt auf dieses Ereignis reagiert, muss man sich die Verdrahtung angucken. Gut, jetzt hat man das verantwortliche Objekt lokalisiert. Man geht in die Klasse und sucht sich die entsprechende Methode. Die Freude hält nicht lange an, denn in der Methode steht auf einmal der Aufruf "Say.Carefully(INeedEinenBestimmtenWert, this)". "OMG!"
Aber zum Glück ist es an der Stelle tatsächlich nicht wichtig, wo der Wert herkommt. Ich suche in der Klasse schnell die passende Receive-Methode und fahre mit der Programmverfolgung fort. In der Receive-Methode wird Business-Logik ausgeführt. Ein neuer Wert wird darin berechnet. (Um Missverständnissen vorzubeugen: Diese Werte sind natürlich auch Objekte. Es sind aber eben spezielle Value-Objekte und keine primitiven Datentypen! Ich bin auch dabei, mir abzugewöhnen, primitive Datentypen direkt zu transportieren, weil diese einfach nichts aussagen und zudem nicht erweiterbar sind.)
Weiter geht's: Am Ende dieser Methode steht dann also wieder "Say.Carefully(SendNeuerWert, this)".
Jetzt geht das Geeier wieder los. "Wer nimmt den Wert entgegen und was passiert damit?". Also wieder zurück und von "oben" drauf gucken, weil ja das Objekt für sich isoliert gewesen ist.
So geht das dann immer weiter. Besonders "lustig" wird es dann, wenn man wirklich einen Bug lokalisieren muss und die Zeit drängt. Da springt man hin und her ... hoch und runter. Wenn man Glück hat, findet man den Bug.

Eine gewisse Ordnung, und trotzdem Chaos
Alles hat im Grunde eine ordentliche Struktur, aber für mich ergibt sich ein Gefühl von Chaos, dass ich auf Dauer nicht zu bändigen vermag. Ursache dessen ist gerade diese Isolation der Objekte! Aus einem Objekt heraus kann ich nicht einfach auf den Programmfluss schließen. Und weil die Objekte äußerlich von nix abhängig sind (fehlende Konstruktorargumente), weiß ich ja nicht mal, ob diese Objekte überhaupt benötigt werden. Ist ein bestimmtes Objekt für einen bestimmten Vorgang unerlässlich? Vielleicht ja? Vielleicht nein? Welche Events von dem Objekt müssen verdrahtet werden, damit die Software funktioniert? Welche davon sind optional?
All das sagt das Objekt, von außen betrachtet, nicht aus.
Ich sehe mir bei einem Softwareprojekt nicht zuerst an, wie die Objekte verdrahtet sind. Der naive Ansatz, den ich immer verfolge, wenn ich wieder mal wissen will, wie etwas funktioniert, ist Folgender:

Im Grunde haben alle meine Anwendungen ein quasi statisches Hauptformular. Dieses nehme ich mir her und im Entwurfsfenster sehe ich es dann mit all seinen Buttons usw. Wenn ich wissen will, welche Funktion sich hinter einem Button verbirgt, klicke ich doppelt drauf und springe in die entsprechende Routine. Bei der MPT beginnt dann jenes Szenario, welches ich oben dargestellt hatte.
Und DAS macht keinen Spaß! Das Formular ist zwar dadurch wunderbar entkoppelt, aber für sich gesehen ist es absolut sinnlos, weil es nix über die Funktionalität der Software aussagt. Nun kann man sich darüber streiten, ob man so beginnen sollte, eine Software zu lesen. Aber, hey! Eine User-Story beginnt schließlich so: "Wenn ich auf den Button klicke, dann ...!" Und dann beginnt die Business-Logik zu fließen. Aber bei der MPT fließt erstmal nix, sondern man kommt gerade an dieser Stelle zum Stillstand, wo es interessant wird. In meinen früheren Projekten ist an der Stelle tatsächlich die Story geflossen. (Auch wenn da in der Anfängerphase(!) eine furchtbare Struktur zum Vorschein kam und die Domänen häufig gemischt waren, wirkte diese weit weniger chaotisch, als die MPT! Denn man konnte wenigstens hintereinander weg dem Programmfluss folgen! Und das ist absolut wichtig, wenn man Software verstehen will!)

Zurück zu den Wurzeln
Eine totale(!) Entkopplung, also Isolation von Objekten, mag ja auch ihre Vorteile haben. Aber mir hat sie in der Praxis das Verstehen meiner eigenen Software nur erschwert. Denn wie gesagt: Ich gucke in erster Linie nicht auf den "Schaltplan", also danach, wie die Objekte miteinander verknüpft sind. Man hat schließlich immer noch Quellcode vor sich, den man wie eine Geschichte lesen können soll.
Das geht aber NICHT, wenn man hier nur isolierte Klassen vor sich hat.
So konnte das nicht weiter gehen! Immer dieses "von Sackgasse zu Sackgasse" springen machte absolut keinen Spaß!
Was würde also dagegen sprechen, Objekten wieder die gewohnte Abhängigkeit zu verleihen? Sie also miteinander lose(!) zu koppeln? Nix würde dagegen sprechen! Die lose Kopplung würde einen aber wieder zu einem lesbaren Programm verhelfen! Und das ist mir wichtiger, als auf biegen und brechen meine Klassen total zu entkoppeln. Das nützt mir nix, wenn ich die Software am Ende nicht vernünftig lesen kann. Schlimmer noch, wenn sich der Lesefluss so anfühlt, als ob ich von einer "goto-Anweisung" zur nächsten "goto-Anweisung" springe. Man nennt das "Spaghetti-Code". Genauso fühlt sich aber auch das Nachvollziehen des MPT-Codes an!

Dependency Injection neu entdeckt: DIY-DI
Dependency Injection (DI) ist mir nicht unbekannt. Durch Misko Hevery, einem regelrechten DI Enthusiasten, hatte ich die Thematik so richtig vertieft. Warum hatte ich aber das Prinzip der DI nicht weiter verfolgt? Im Grunde hatte mich die manuelle Objekterzeugung abgeschreckt. Sowas kann dann schon mal unübersichtlich und komplex werden. Natürlich gibt es auch DI-Frameworks, welche einem die Arbeit abnehmen sollen.
Wenn ich aber eines hasse, dann sind es irgendwelche Frameworks, von denen man sich wieder abhängig macht. Frameworks, wo im Hintergrund irgendwas "Magisches" passiert, kann ich nicht leiden. Ich möchte in meinem Quellcode jede Kleinigkeit nachvollziehen können. Dass ich da beim .Net-Framework mit einem relativ hohen Abstraktionsgrad leben muss, ist mir da schon genug "Magie". Da brauche ich nicht noch irgendein mysteriöses Framework, wo ich nicht weiß, was da im Detail passiert.

Nun bin ich da aber vor kurzem bei Misko Heverys Blog auf einen Artikel gestoßen, der in mir wieder das Interesse für DI geweckt hat: Do it Yourself – Dependency Injection (DIY-DI). (Der eigentliche "Erfinder" dieser Idee ist aber Chad Parry und sein originaler Beitrag befindet sich dort.)
Als ich mir die Präsentation und das PDF dazu durchgelesen hatte, war mir schlagartig klar, dass ich meine Software zukünftig SO und nicht anders entwickeln möchte! Das ist genau das, was ich unter sauberen Quellcode verstehe.
DI ist im objektorientierten Bereich nun mal ein probates Mittel, um Ordnung in seine Struktur zu bekommen. Wenn ein Objekt auf andere Objekte angewiesen ist, damit es funktionieren kann, sollte es dies doch äußerlich kenntlich machen. Das gibt eine gewisse Transparenz und zugleich eine enorme Sicherheit für den Programmierer. Man sieht sofort, mit welchen Objekten das Zielobjekt unmittelbar interagiert und man weiß, dass das Objekt voll funktionsfähig ist, wenn es die geforderten Objekte injiziert bekommt. (Zumindest sollte das so sein. Stichwort: "Law of Demeter")

So habe ich auf Arbeit damit begonnen, mein derzeitiges Projekt nach DIY-DI zu migrieren. Und mir bereitet es erstmals wieder Freude, Funktionalität hinzuzufügen.
So begann meine Überlegung: "Das Business-Model benötigt kein User Interface, um zu funktionieren. Aber ein User Interface benötigt ein Business-Model, um zu funktionieren."
Das erscheint auch logisch. Spinnt man das noch eine Ebene weiter, so benötige ich als Benutzer das User Interface, um die Software bedienen zu können! Das ist Fakt!
Ich, als Mensch, bin ja auch nicht isoliert von meiner Umwelt. Die Umwelt ist zwar nicht abhängig von mir, aber ich von ihr. So brauche ich z.B. die Luft zum Atmen, usw.

Um zur Software zurück zu kommen, injizierte ich also einen "MainPresenter" in meine "MainForm". Und über den "MainPresenter" fordere ich nur die Dinge an, die für das User Interface von Belang sind. Der "MainPresenter" ist von diversen Service-Objekten abhängig, die er benötigt, um die Anforderungen vom User Interface erfüllen zu können.
So hatte ich schon einige Funktionalitäten von der MPT-Architektur umgestellt. Alles ist auf einmal so einfach nachvollziehbar geworden. Der Aufruf, "Gehe zu Definition" in Visual Studio, ist mir endlich wieder von Nutzen, weil ich mit dessen Hilfe wirklich hintereinander weg die komplette Aufrufhierarchie nachvollziehen kann.

Das soll es erstmal soweit gewesen sein. Die totale Entkopplung ist für mich einfach nicht mehr attraktiv.

Montag, 31. Mai 2010

Mittelmänner bleiben, wie sie sind!

In meinem letzten Beitrag war ich ja völlig begeistert von der Idee, die Infrastruktur der Asynchronisation und Synchronisation in die Mittelmänner zu verschieben.

Davor will ich aber an dieser Stelle ausdrücklich abraten!

Ralf Westphal hatte mich davor gewarnt, die "Vermittler" mit mehr Aufgaben zu beladen, als sie eigentlich tun sollten. Und da hat er absolut Recht!

Warum habe ich aber mit meiner Grundidee gebrochen und die Vermittler mit der Synch/Asynch-Angelegenheit belastet?
Ganz einfach: Ich hatte einen Denkfehler. *g*

Ich hatte einfach zu kompliziert gedacht. Das möchte ich mal genauer erklären:
Mein Gedanke ging in die Richtung, wie es auf dem folgenden Bild dargestellt ist.

Für den Asynch/Synch Aspekt werden eigene Mittelmänner und Schnittstellen definiert. (Zum Vergrößern auf Bild klicken.)

Meine Idee war es, für die Asynchronisation und Synchronisation auch entsprechend eigene Mittelmänner und Schnittstellen zu definieren. Das erschien mir aber viel zu unflexibel und zu aufwändig, weshalb ich mich für den unschönen Hack entschied, die Mittelmänner zu modifizieren. Zudem sah ich hierin schon fast das Todesurteil meiner MPT. Denn wenn ich mit der MPT nicht ebenso einfach solche flexiblen Konstrukte realisieren kann, wie es Ralf mit seinen EBCs dort gezeigt hat, so wäre die MPT schlicht unbrauchbar.

Und ich war wirklich schon nahe dran, die MPT zu vergessen und mich den EBCs zuzuwenden.

Nun, ich weiß nicht, warum ich erst letzten Freitag beim Autofahren auf die entscheidende Idee gekommen bin, welche die MPT ebenso flexibel macht, wie die EBCs. Dabei ist es im Nachhinein betrachtet schon fast peinlich, dass ich so umständlich gedacht habe.

Die Lösung zum Problem zeigt die folgende Grafik:

Hier werden einfach die bestehenden Mittelmänner und Schnittstellen verwendet. (Zum Vergrößern auf Bild klicken.)

Anstatt für die Asynch/Synch Aspekte eigene Mittelmänner und Schnittstellen zu implementieren, kann ich doch auch einfach die bestehenden Konstrukte verwenden. Denn die Asynch/Synch Modelle benötigen doch gar keine andere Signatur. Das heißt, es sind ja keine Adapter bzw. Translator, welche z.B. einen Text aufnehmen und eine Zahl zurückgeben. Oder die ein Trigger-Event aufnehmen und verschiedene konkrete Events auslösen.
Das war von Anfang an mein Denkfehler dabei.
Die Asynch/Synch Modelle sind doch in dem Falle einfach nur "Repeater". Sie können doch gleichzeitig der "Caller und Listener" oder "Transmitter und Receiver" sein. Das verbietet niemand.
Und das beste hierbei ist eben wieder der Vorteil der konkreten Trennung der Objekte. Die "MainForm" und das "BerechneModel" wissen nix von einer asynchronen oder synchronen Verarbeitung. Für sie ist alles so, wie immer. Und das ist ja auch der Sinn und Zweck bei der MPT (und auch bei Ralfs EBCs)! Man soll die Infrastruktur der Anwendung verändern können, OHNE die bestehende Business-Logik auch nur minimal zu verändern.
Was mir bei dem zweiten Bild besonders gefällt, ist die kettenartige Struktur, die man dadurch erhält. Man könnte hier sogar beliebig viele Zwischenglieder einfügen, denn der Input und Output ist für die Endknoten immer der Selbe!
Die Asynchronisaton und Synchronisation spielt sich nur in den jeweiligen Modellen und nirgendwo anders ab!
Und DAS ist wirklich genial! ;-)
(Heute hatte ich das auf Arbeit auch gleich in meinem Projekt umgeändert. Und es funktionierte anstandslos.)

Wie sieht nun der Quellcode dazu aus? Ich freue mich, sagen zu können, dass es fast so aussieht, wie bei Ralfs Asynch/Synch-Beispiel! :-)

So wird das zusammengesteckt:
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace NewAsyncTest
{
 static class Program
 {
  /// <summary>
  /// Der Haupteinstiegspunkt für die Anwendung.
  /// </summary>
  [STAThread]
  static void Main()
  {
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);

   Form1 form1 = new Form1();
   BerechneModel berechneModel = new BerechneModel();
   Asynchronizer asynchronizer = new Asynchronizer();
   Synchronizer synchronizer = new Synchronizer();

   new BerechneMediator(
    form1,
    asynchronizer);

   new BerechneMediator(
    asynchronizer,
    berechneModel);

   new ErgebnisFrequency(
    berechneModel,
    synchronizer);

   new ErgebnisFrequency(
    synchronizer,
    form1);

   Application.Run(form1);
  }
 }
}

So sieht der Asynchronizer aus:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace NewAsyncTest
{
 public class Asynchronizer :
  IAmBerechneListener,
  IAmBerechneCaller
 {
  public void DoBerechne()
  {
   ThreadPool.QueueUserWorkItem(
    delegate
    {
     Say.Carefully(Berechne, this);
    });
  }

  public event EventHandler Berechne;
 }
}

So sieht der Synchronizer aus:
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Threading;

namespace NewAsyncTest
{
 public class Synchronizer :
  IAmErgebnisReceiver,
  IAmErgebnisTransmitter
 {
  private AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);

  int ergebnis;
  public void ReceiveErgebnis(int ergebnis)
  {
   asyncOp.Post(new SendOrPostCallback(
    delegate
    {
     this.ergebnis = ergebnis;
     Say.Carefully(SendErgebnis, this);
    }), null);
  }

  public event EventHandler SendErgebnis;

  public int ReturnErgebnis()
  {
   return ergebnis;
  }
 }
}

Ein entsprechendes RAR-Archiv, mit dem Beispielprogramm, kann man sich auch wieder herunterladen: KLICK

Tja, keine Ahnung, aber beim Autofahren kamen mir bisher immer die besten Einfälle. *g*

Ich werde wohl im nächsten Beitrag mal die MPT mit den EBCs vergleichen. (Wobei ich die EBCs nicht als "Konkurrenz" betrachte. Das erkläre ich aber, wenn es soweit ist.)

Dienstag, 11. Mai 2010

Die Mittelmänner entwickeln sich!

Ich bin ja so glücklich über das, was ich heute erreicht habe! Aber schön der Reihe nach:

Die Mittelmänner entwickeln sich weiter!

Wie kann man das verstehen? Ganz einfach: Weißt Du noch, als ich in meinen Präsentationen (z.B. diese hier) gesagt habe, dass die Mittelmänner selber von Außen keine Zugriffsmöglichkeiten haben (sollen)?
Ich habe heute meine Meinung geändert! (Ich habe aber auch nie gesagt, dass die Micro-Pattern-Trilogie bereits vollendet ist.) ^_^

Ich bin bei Ralf Westphals Blog über diesen Beitrag gestolpert: Asynchrone Kommunikation mit EBCs statt “Async-Pattern”

Ralf hat da absolut Recht, wenn er sagt, dass die Dinge, wie z.B. Threads, nix in der Businesslogik zu suchen haben! Ich hatte aber auch am Anfang den Fehler gemacht, als ich mal diesbezüglich experimentiert hatte. Ich hatte aber auch keine Idee, wie ich es denn am besten anders anstellen könnte.

Und heute grübelte ich etwas über diesen Fakt nach. Die natürliche Trennung zwischen den Objekten stellen ja die Mittelmänner dar. Also ist es doch naheliegend, wenn man an diesem Connector festlegt, ob der Nachrichtenfluss asynchron oder synchron verlaufen soll und nicht in den Businessmodellen!

Guter Stil ist es ja, wenn man ein Framework so entwirft, wie man es später auch auf einfache Weise verwenden möchte. So kam ich auf folgende Idee:

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace NewAsyncTest
{
 static class Program
 {
  /// <summary>
  /// Der Haupteinstiegspunkt für die Anwendung.
  /// </summary>
  [STAThread]
  static void Main()
  {
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);

   Form1 form1 = new Form1();
   BerechneModel berechneModel = new BerechneModel();

   new BerechneMediator(
    form1,
    berechneModel).Asynchronize();

   new ErgebnisFrequency(
    berechneModel,
    form1).Synchronize();

   Application.Run(form1);
  }
 }
}

Hier nochmal die relevante Stelle:

new BerechneMediator(
 form1,
 berechneModel).Asynchronize();

new ErgebnisFrequency(
 berechneModel,
 form1).Synchronize();

Wie man sieht, haben die Mittelmänner nun doch noch öffentliche Methoden bekommen.
Die "form1" ist der Auslöser für den "berechne"-Vorgang. Das "berechneModel" führt die Berechnung durch. Je nachdem, wie umfangreich die Berechnung ist, kann dies eine gewisse Zeit dauern. Deshalb wollen wir diesen Vorgang asynchron durchführen. Das hat zur Folge, dass der Nutzer in der Zwischenzeit ungehindert weiter mit der Anwendung arbeiten kann.

Wenn das "berechneModel" fertig ist, wird das Ergebnis einfach an die Form zurück gesendet. Aber Vorsicht! Es muss synchronisiert werden, weil die "form1" ein GUI ist. Tut man dies nicht, wird eine Exception geworfen!
Mit dem Methodenaufruf "Synchronize" ist das Thema aber schon gegessen.
So einfach kann die asynchrone/synchrone Nachrichtenverarbeitung sein. Und übersichtlich ist das ganze auch noch.
Ist das geil, oder ist das geil? ^_^

Und wie sieht die Sache intern aus?
Der Übersichtlichkeit halber, zeige ich hier nur mal den "BerechneMediator":

using System;
using System.Collections.Generic;
using System.Text;

namespace NewAsyncTest
{
 public interface IAmBerechneCaller
 {
  event EventHandler Berechne;
 }

 public interface IAmBerechneListener
 {
  void Berechne();
 }

 public class BerechneMediator : Middleman
 {
  private readonly IAmBerechneCaller caller;
  private readonly IAmBerechneListener listener;

  public BerechneMediator(
   IAmBerechneCaller caller,
   IAmBerechneListener listener)
  {
   this.caller = caller;
   this.listener = listener;

   this.caller.Berechne += Do(caller_Berechne);
  }

  void caller_Berechne(object sender, EventArgs e)
  {
   listener.Berechne();
  }
 }
}

Kein Witz! So sieht das wirklich aus. Mein Ziel war es ja, dass ich meine Pattern genauso weiterverwenden kann, wie ich es bisher getan habe.
Man sieht erst auf den zweiten Blick, was sich geändert hat. JEDER Mittelmann muss ab sofort von der abstrakten Klasse "Middleman" erben, wenn er die komfortablen synch/asynch Features nutzen will.
Wenn er diese geerbt hat, muss er beim Zuweisen des Event-Handlers die geschützte "Do"-Methode verwenden. Mehr ist hierbei absolut nicht zu tun.

Sehen wir uns nun die abstrakte Basisklasse "Middleman" an. Denn nur dort befindet sich die gesamte Infrastruktur.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.ComponentModel;

namespace NewAsyncTest
{
 public abstract class Middleman
 {
  private bool isAsynchronizer = false;
  private bool isSynchronizer = false;
  private AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);

  public void Asynchronize()
  {
   isSynchronizer = false;
   isAsynchronizer = true;
  }

  public void Synchronize()
  {
   isSynchronizer = true;
   isAsynchronizer = false;
  }

  public void NormalMode()
  {
   isSynchronizer = false;
   isAsynchronizer = false;
  }

  protected EventHandler Do(EventHandler customMethod)
  {
   return new EventHandler(
    delegate
    {
     if (isAsynchronizer)
     {
      ThreadPool.QueueUserWorkItem(delegate { customMethod(this, EventArgs.Empty); });
     }
     else if (isSynchronizer)
     {
      asyncOp.Post(new SendOrPostCallback(
       delegate
       {
        customMethod(this, EventArgs.Empty);
       }), null);
     }
     else
     {
      customMethod(this, EventArgs.Empty);
     }
    });
  }
 }
}

Sieht kompliziert aus, ist es aber gar nicht. ;-)
Es sind die anonymen delegates, die das Ganze etwas wüst aussehen lassen. *lach*
Über die 3 öffentlichen Methoden werden lediglich Statusflags gesetzt. (Ja, es sind 3 und nicht 2 Methoden, weil ich noch eine Methode wollte, mit der ich die Flags wieder zurücksetzen kann.)

In der geschützten "Do" Methode wird lediglich ein neuer EventHandler zurückgegeben. Und in diesem Handler steckt eine Kontrollanweisung, die einfach prüft, welche Rolle der "Mittelmann" im Moment spielt. Ist er ein "Asynchronizer", wird die "customMethod" einfach in den ThreadPool gesteckt. Und nach mir die Sintflut. *g*
Ist er ein "Synchronizer", wird die "customMethod" in die "asyncOp" gesteckt. (Dieses Ding sorgt voll automatisch dafür, dass ein beliebiger Thread mit dem GUI-Thread synchronisiert wird.)
Wenn er letztlich nix von Beiden ist, dann soll die "customMethod" ganz normal aufgerufen werden.

Und weil diese Prüfung bei JEDEM ausgelösten Event durchgeführt wird, könnte man quasi zur Laufzeit der Anwendung das Verhalten ändern.

Also ich finde das richtig geil! Da hier in der grundlegenden Struktur absolut nicht viel geändert werden muss, habe ich das heute auch in sehr kurzer Zeit in meinem Softwareprojekt auf Arbeit aktualisiert. Da habe ich nämlich auch einen Task, der am besten asynchron ablaufen müsste. Das ist jetzt ein Kinderspiel! :-D
Es ist so geil! ^_^

Das soll es auch schon gewesen sein.
Ich habe hier auch ein Visual Studio 2008 Projekt, wo Du das mal ausprobieren kannst: KLICK [rar-Archiv]

1. MPT Beispiel - Teil 2

Nun kommt, wie vesprochen, der 2. Teil meines ersten MPT Beispiels.
Und ich bin direkt froh darüber, dass ich gerade in diesem Teil auf die "Mittelmänner" eingehe! Denn erst heute habe ich einen effektiven Weg gefunden, um ganz einfach die asynchrone und synchrone Nachrichtenverarbeitung zu verwenden. Dazu aber später mehr. (Das wird dann ein extra Beitrag werden.)

Beim letzten Mal hatte ich ja gezeigt, wie einfach das Hauptfenster intern aufgebaut ist.

Heute möchte ich zeigen, was die Komponenten zusammenhält:

Die Mittelmänner

Hier kommt erstmal wieder das Listing des Quellcodes, den ich nachher ausführlicher erklären möchte:

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace PFDynamic
{
 static class Program
 {
  /// <summary>
  /// Der Haupteinstiegspunkt für die Anwendung.
  /// </summary>
  [STAThread]
  static void Main()
  {
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);

   Form1 form1 = new Form1();
   AddTextViewModel addTextViewModel =
    new AddTextViewModel(
     new TextViewManager(
      form1));
   
   new AddTextViewMediator(
    form1,
    addTextViewModel);

   new TextViewFrequency(
    addTextViewModel,
    form1);

   Application.Run(form1);
  }
 }
}

Ja, das ist auch schon alles.
Los geht's!

Form1 form1 = new Form1();
AddTextViewModel addTextViewModel =
 new AddTextViewModel(
  new TextViewManager(
   form1));

Ich muss es gleich jetzt sagen: Was du in diesem Ausschnitt siehst, ist Mist! Es funktioniert zwar, aber es ist strukturell einfach nicht zumutbar, wenn du mit den Micro-Pattern entwickelst! Dass ich es so programmiert habe, lag eigentlich nur an zwei Faktoren:
1. wollte ich die Idee nur mal schnell testen, um zu sehen, ob das so funktioniert, wie ich es mir gedacht habe.
2. hing ich gedanklich doch noch sehr an dem originalen "Presenter Fist" Prinzip. Denn dort ist es gestattet, dass konkrete Objekte eigene Abhängigkeiten haben.

Ich will dennoch erklären, was der Code zu bedeuten hat.
In Zeile 18 wird einfach nur das Hauptfenster erzeugt, ist aber noch nicht sichtbar.
In der nächsten Zeile wird das "AddTextViewModel" erzeugt. Dieses erwartet als Konstruktor-Argument ein Objekt, welches sich als "IAmTextViewFactory" bezeichnet. Der "TextViewManager" fungiert als diese "IAmTextViewFactory".
Der "TextViewManager" erwartet ein Objekt, welches sich als "IAmTextTransmitter" bezeichnet. In diesem Falle ist ja das Hauptfenster eben dieser "TextTransmitter".
Die Internas dieses Konstrukts zeige ich im nächsten Teil.

Wie man sieht, ist sogar diese kleine Hierarchie nicht mehr leicht nachvollziehbar. Zudem sind die Objekte wieder miteinander gekoppelt und somit voneinander abhängig. Sowas geht echt gar nicht! Kein Objekt sollte von einem anderen Objekt abhängig sein ... ausgenommen, die "Mittelmänner". Aber die haben selber keine Businesslogik, sondern sind nur für die Infrastruktur da.

Der nächste Abschnitt:

new AddTextViewMediator(
 form1,
 addTextViewModel);

new TextViewFrequency(
 addTextViewModel,
 form1);

So sollte die gesamte Software zusammengesetzt sein! Das ist die saubere Anwendung vom MPT Konzept.
Die Mittelmänner können einfach in den "Raum" hinein erstellt werden.

Sehen wir uns einfach mal an, wie die "Mittelmänner" im Inneren aussehen:

Der Mediator:

using System;
using System.Collections.Generic;
using System.Text;

namespace PFDynamic
{
 public class AddTextViewMediator
 {
  private readonly IAmAddTextViewCaller caller;
  private readonly IAmAddTextViewListener listener;

  public AddTextViewMediator(
   IAmAddTextViewCaller caller,
   IAmAddTextViewListener listener)
  {
   this.caller = caller;
   this.listener = listener;

   this.caller.AddTextView += new EventHandler(caller_AddTextView);
  }

  void caller_AddTextView(object sender, EventArgs e)
  {
   listener.DoAddTextView();
  }
 }
}

Die Frequency:

using System;
using System.Collections.Generic;
using System.Text;

namespace PFDynamic
{
 public class TextViewFrequency
 {
  private readonly IAmTextViewTransmitter transmitter;
  private readonly IAmTextViewReceiver receiver;

  public TextViewFrequency(
   IAmTextViewTransmitter transmitter,
   IAmTextViewReceiver receiver)
  {
   this.transmitter = transmitter;
   this.receiver = receiver;

   this.transmitter.SendTextView += new EventHandler(transmitter_SendTextView);
  }

  void transmitter_SendTextView(object sender, EventArgs e)
  {
   receiver.ReceiveTextView(
    transmitter.ReturnTextView());
  }
 }
}

Soviel gibt es da im Grunde nicht zu sagen, denke ich. Wer C# kann, sollte den Quellcode hier problemlos verstehen. Es ist im Grunde nur Wrapper-Code. Und diesen Code muss man bei jedem Triplet erstellen. Und das langweilt, weshalb hier unbedingt ein RAD-Tool her muss! ^_^

Das soll es für diesen Beitrag gewesen sein.

Jetzt schreibe ich gleich noch den nächsten Beitrag mit den Neuigkeiten, die sich in letzter Zeit bei dem MPT Konzept ergeben haben.

Bis dann! :-)

Montag, 10. Mai 2010

Ich mache es doch anders ...

Vor ein paar Tagen hatte ich ja geschrieben, dass die Fortsetzung des MPT-Beispiels etwas warten muss, weil ich das RAD-Tool aktiv entwickeln möchte.
Ich werde es aber doch so machen, dass ich erst noch das MPT-Beispiel komplett erkläre. Das ist so besser, weil dann erstmal eine Basis dafür existiert, wie die Sache in der Praxis aussieht.

Umso spannender (ich hoffe es jedenfalls *g*) wird es dann nach dem Beispiel werden!
Ich habe nämlich vor, den Fortschritt des RAD-Tools per Video zu protokollieren. Es soll aber keine Schritt-für-Schritt Anleitung werden, sondern ich möchte nur kurz die Features vorstellen und erklären, wie diese funktionieren. Das werde ich sicher auch mal mit Diagrammen veranschaulichen.
In jedem Beitrag werde ich dann auch noch den Versions-Hash von dem entsprechenden Git-Commit hinterlassen, damit man später irgendwann mal leicht nachvollziehen kann, zu welcher Version das Video gehört.
Ich denke, das ist mal was anderes.
Ich, persönlich, liebe es ja, zu verfolgen, wie eine Anwendung wächst und an Features gewinnt. (Das war z.B. auch ein Grund, weshalb ich schon möglichst zeitig zu Google Chrome gegriffen habe. Da waren ja, verglichen mit Firefox, noch kaum Features drin. Oder die Entwicklung von Blender verfolge ich z.B. auch schon, seit es noch zu "Not a Number" gehörte. Ja, einer Anwendung beim Wachsen zuzusehen ist echt aufregend!)

Momentan ist "Atomix" ja noch in einem sehr frühen (und nicht wirklich brauchbaren) Stadium. Aber es besitzt dennoch schon genug, um diesen ersten Iterationsschritt per Video vorzustellen. Ich kann z.B. Dateien laden und diese in einer Listbox anzeigen, welche sich in einem eigenen Toolfenster befindet. Über einen Menüpunkt kann ich dieses Fenster beliebig öffnen und schließen.
Das zeige und erkläre ich aber alles in dem Video genauer. Es ist aber alles unter Zuhilfenahme der MPT entstanden.
"Atomix" wird auch eher evolutionär wachsen. In der ersten Iteration werden die Features noch statisch bzw. spezifisch sein, um zunächst festzustellen, ob es so funktioniert. In der nächsten Iteration werden die Features dynamischen Charakter haben bzw. abstrakter sein. So ist es z.B. auch momentan bei der Dateiansicht. Sie befindet sich momentan im ersten Iterationsschritt. (Genaueres dazu später in dem Video.)


Dann muss ich unbedingt noch etwas berichten!

Heute wurde ich von Ralf Westphal per eMail kontaktiert und erhielt ein interessiertes Feedback. Er verwies mich auch auf seine Idee der sogenannten "Event-based Components". Unsere Ideen sind im Grunde identisch! Das heißt, wir beide streben ein Konzept an, womit man auf einfache Weise durch völlige Entkopplung der Aspekte seine Anwendung agil und flexibel entwickeln kann.
Man sollte sich seine Ideen dazu wirklich mal ansehen! Gerade seine Grafiken sind sehr anschaulich.
Was auf den ersten Blick auffällt, ist, dass er regen Gebrauch der neuen C# Features macht.
Ich halte mich dabei noch an die Version 2.0, weil ich auf Arbeit auch noch mit Visual Studio 2005 Professional auskommen muss. Und mein Konzept möchte ich in erster Linie für die Arbeit nutzen. Daher ist das etwas problematisch, mich zu sehr auf die neuen Features einzulassen. Man ist dann schnell davon verwöhnt.
(So brachte mich z.B. die erste richtige Konfrontation mit C# direkt von C++ ab. C/C++ fasse ich nicht mehr freiwillig an. Nur, wenn es wirklich sein muss.)

Zudem bin ich überrascht, dass solche Ideen zur Abwechslung mal aus dem deutschsprachigen Raum kommen. Damit hätte ich absolut nicht gerechnet!

Wir wollen auf alle Fälle weiterhin Kontakt halten und Ideen austauschen.

Aber ich bin wirklich froh, dass es noch mehr Leute gibt, die sich über die Zukunft der Softwareentwicklung Gedanken machen! :-)

Samstag, 8. Mai 2010

Das Rad neu erfinden? Nein!

Heute Früh, zwischen 2 und 3 Uhr bin ich plötzlich aufgewacht und konnte nicht mehr einschlafen. Ohne Witz!
Mir gingen die ganze Zeit Ideen für weitere Features meiner begonnenen "Atomix" Anwendung durch den Kopf. Sind die Ideen mit dem MPT Konzept umsetzbar? Die Gedankenexperimente gingen los und ich kam immer zu dem selben Ergebnis: Natürlich ist das umsetzbar! Unter Zuhilfenahme der MPT ist das sogar sehr einfach, da man sich ja keine Gedanken über die Objektabhängigkeiten machen muss. Man geht ja bei der Implementation einfach davon aus, dass man die benötigten Daten einfach von irgendwoher bekommt. Und dann nutzt man einfach diese Daten für sein gewünschtes Feature.
Früher habe ich mir eher den Kopf darüber zerbrochen, wie ich die Objekte am Besten miteinander verkopple, um zu dem gewünschten Feature zu kommen:
Injiziere ich Objekt "A" in Objekt "B", oder doch besser Objekt "B" in Objekt "A"? Und was ist, wenn ich in Objekt "A" nur einen Teil der Daten von Objekt "B" benötige? Vielleicht benötige ich nur einen bestimmten String von Objekt "B"? Weshalb also Objekt "A" vom kompletten Objekt "B" abhängig machen? Ich kann es aber auch nicht anders machen, weil ich z.B. den String irgendwann dynamisch zur Laufzeit benötige. Also muss ich es doch so machen.
Und wenn ich in Objekt "B" noch mit Objekt "A" hantieren will? [usw. usf.]
Diese Überlegungen entfallen bei der MPT völlig, da hier im Grunde alle Objekte auf der selben Ebene stehen. Und kein Objekt interessiert sich dafür, wo es seine Daten herbekommt oder wo seine eigenen Daten hingehen. Man benötigt eine bestimmte Datenstruktur für Irgendwas Bestimmtes? Passende Schnittstelle dafür implementieren und seine Idee fortsetzen.

Dadurch sind massenhaft Features durch meinen Kopf gegangen. Das hat kaum ein Ende genommen. Echt schlimm, wenn man dabei durch nix gebremst wird ... höchstens seine eigene Fantasie. Und ehe ich mich versah, ging schon wieder die Sonne auf. *g*

Aber dennoch kam ich an einen Punkt, der mich heute den ganzen Tag stark beschäftigt hat:
"Atomix" wird auch Quellcode parsen müssen! Soll ich da anfangen, einen eigenen Parser zu entwickeln oder eine existierende Lösung suchen?

Ich dachte tatsächlich erst daran, einen eigenen Parser zu entwickeln. Schnell mal nach der offiziellen C# Spezifikation gegoogelt und den Standard ECMA-334 gefunden. "Rein geguckt und zam gezuckt!", wie es so schön heißt. ^_^
Dass C# komplex ist, ist klar. Aber dieses Dokument hat es schon in sich. Eben eine richtige Spezifikation.

Ich hielt dennoch an dem Gedanke fest, einen eigenen Parser zu entwickeln. Aber mir fehlte ein bisschen der konkrete Ansatz, wie man sowas programmiert. Also nochmal gegoogelt und ich fand das: "Writing a parser: overview".

Nun war die dort erfundene Beispielsprache ja recht einfach. Einfacher, als etwa C#. Und dennoch ist der Parser für jene einfache Sprache so komplex. Wie würde der erst bei C# aussehen? So einen Parser hat man jedenfalls nicht mal so eben nebenbei programmiert.

Nun will ich ja im Grunde nur ein RAD-Tool entwickeln, welches mir ein bisschen Arbeit abnehmen soll. Und der dafür benötigte Parser ist da eher ein notwendiges Übel. Der soll bei der Entwicklung nicht mehr Aufmerksamkeit bekommen, als meine eigentliche Anwendung.
So kommen wir wieder auf die Überschrift dieses Beitrages zurück. Ist es notwendig, das Rad neu zu erfinden? Also in dem Falle, sicher nicht! Ich kenne mindestens ein Projekt, welches einen Parser verwendet, wie ich ihn benötige: "SharpDevelop".
Ich lud mir den Sourcecode von SharpDevelop runter und stöberte darin nach etwas, was "Parser" hieß. Ich hatte auch etwas gefunden, aber es war nicht direkt das, was ich erwartet hatte.
Also googelte ich nochmal, weil ich wissen wollte, wie man den Parser von SharpDevelop für eigene Projekte nutzen kann. Tatsächlich fand ich auch etwas: "NRefactory".
Das sieht sehr vielversprechend aus und scheint einfach verwendbar zu sein. (Und wenn man einmal weiß, wonach man suchen muss, findet man es auch gleich im Quellcode von SharpDevelop wieder. *g*)
Ich fand auch noch eine Auflistung von verschiedenen Parsern bei Google: "Parser for C#".

Hmmm, dort fiel mir auch die "Common Compiler Infrastructure: Metadata API" ins Auge. Das werde ich mir vielleicht auch mal näher ansehen.
Aber theoretisch dürfte für meine Zwecke die normale "System.Reflection" ausreichen. Mal gucken.

Tja, nun hätte sich im Grunde die Sache mit dem Parser erledigt und ich kann mich wieder voll und ganz auf meine eigentliche Anwendung konzentrieren. :-)

Mittwoch, 5. Mai 2010

Was gibt es Neues?

Zunächst mal: Keine Angst! Das MPT-Beispiel ist nicht in Vergessenheit geraten. Es muss jedoch erstmal etwas warten. (Warum, erkläre ich weiter unten.)

Auf Arbeit habe ich momentan die Zeit, meine Softwareprojekte aufzuräumen. (Das heißt, ich nehme mir die Zeit einfach, wenn sonst nix zu tun ist. *g*) Und da dachte ich mir so, dass ich mein letztes (noch aktuelles) Projekt restrukturiere. Gerade, weil ich weiß, dass da später noch mehr Features implementiert werden sollen. Und bei der alten Struktur wäre das "Frickeln" vorprogrammiert. ;-)

Also entschied ich mich, die eher frickelhafte (aber funktionale) Software in eine ordentliche MPT umzuformen. Ganz einfach ist das nicht, da in der alten Struktur viele verschiedene Aspekte in einer einzigen Klasse zusammengefasst sind. Das Meiste von der Business-Logik sammelte sich leider in der GUI-Klasse an. (Wenn es schnell gehen soll und man noch den Überblick behält, kann man schon mal diesen Schritt wählen. Denn Merke: Der Kunde interessiert sich am Ende nur für das Tool und nicht über dessen interne Struktur. There is no perfect Design!)
Aber man tut sich selber einen großen Gefallen, wenn man die Aspekte seiner Software voneinander trennt!
Tja, was soll ich sagen? Ich freue mich immer wieder, dass das MPT Konzept tatsächlich so funktioniert, wie ich es mir gedacht habe. Nebenbei entdecke ich Möglichkeiten, wie ich es noch optimieren kann, um mir etwas Arbeit abzunehmen und den Überblick zu behalten.

Dabei kommen wir gleich zum nächsten Punkt: Es muss leichter fallen, Software mit dem MPT Konzept zu erstellen!

Dafür muss unbedingt ein RAD-Tool her! Da es sowas für die MPT noch nicht gibt, ist mein Einsatz gefragt. *g*
Vergangenes Wochenende hatte ich schon mit der Entwicklung begonnen. Der Projektname lautet "Atomix". (Der Name hat sich so ergeben, weil man mit dem Tool seine Software so erstellt, wie wenn man einzelne Atome miteinander verbindet.) Ich möchte mal kurz erklären, wie ich mir dieses Tool vorstelle:
Es soll von Anfang an mit dem MPT Konzept entwickelt werden. Ich möchte es so zeitig wie möglich produktiv einsetzen können. Das heißt also, die Features haben Vorrang und das Design kommt später. Es soll schließlich in erster Linie ein Werkzeug sein und seinen Zweck erfüllen. (Das Design sollte sich aber später leicht aufpeppen lassen. Der völligen Entkopplung sei Dank! *g*)

Dieses Tool soll vorerst nicht die gewohnte Entwicklungsumgebung ersetzen, sondern diese ergänzen. (Aber nicht in Form eines Plug-Ins!)
Im Vordergrund steht die MPT-Struktur der zu entwickelnden Software. Aus diesem Grund wird diese in einer grafischen Form dargestellt werden. Wie man sich das ungefähr vorstellen kann, soll folgende Grafik zeigen:

So könnte die grafische Repräsentation mal aussehen.
(zum Vergrößern auf's Bild klicken)

Dieses Diagramm hatte ich mit yEd entworfen und zeigt den funktionalen Beginn meines ersten "Atomix"-Versuches. (Dies verfolge ich aber nicht weiter, weil ich mich hierbei noch zu sehr an der Idee des "Presenter First" orientiert hatte. Das heißt, hier haben die konkreten Objekte selber feste Abhängigkeiten. Man achte auf die zusammenhängenden weißen Objekte mit der dicken Linie. Das darf nicht sein, da es ein Stilbruch bei der MPT ist.)

Was man aber gerade an diesem Diagramm sehr gut sehen kann, ist die organische Form, welche die Software letztlich hat. Sie ist nicht starr und steif, sondern anpassbar und flexibel.

Bei meinem Tool steht also die Grafik im Mittelpunkt. Wenn man dann z.B. auf eines der Elemente klickt, soll ein Fenster aufpoppen, wo der zugehörige Quellcode drin steht. Dazu inspiriert hat mich der neue Ansatz der sogenannten "Code Bubbles".
Man soll dann nur den Quellcode sehen, der in dem Moment wirklich relevant ist.
Hinzufügen von neuen Triplets soll mit wenigen Mausklicks möglich sein. Dabei soll sich stehts der Quellcode automatisch mit anpassen. Die Grafik soll sich auch aktualisieren, wenn ich z.B. in Visual Studio den Quelltext ändere.
Die einzelnen Elemente sollen durch einfaches Drag & Drop austauschbar sein, sodass man seine Software im Grunde durch wenige Klicks verändern kann.

Das sollen erstmal die groben Ideen sein, die mir spontan eingefallen sind.
Aber alles schön der Reihe nach. *g*

Naja, und dafür muss erstmal die Fortsetzung von meinem MPT Beispiel etwas zurückstehen.

Samstag, 24. April 2010

1. MPT Beispiel - Teil 1

So, jetzt gibt es das erste praktische Beispiel zur Micro-Pattern-Trilogie.
Ich hatte gründlich darüber nachgedacht, ob ich extra für jedes Triplet eine eigene kleine Anwendung entwickeln soll, oder nicht. Aber ich dachte mir so, für das CML Triplet lohnt sich keine eigenständige Anwendung, weil es schlicht zu primitiv ist.
Ich hatte da aber schon mal etwas probiert, was vom Umfang her einem realistischen Beispiel würdig ist. Da kommt im Prinzip alles vor, außer des CPS Triplets. (Das ist aber nicht schlimm. Das kommt dann einfach ein anderes Mal dran.)
Weil Bilder bekanntlich mehr sagen, als tausend Worte, stelle ich die Anwendung einfach mal in einem kurzen Video vor:


(Oh Gott! Da kommt man sich teilweise blöd vor, wenn man mit 'nem Mikrofon spricht. Naja, ich bin nicht so der Rede-Mensch. Aber irgendwie fetzt das trotzdem! Ich werde sicher noch mehr Videos dieser Art machen. Aber da wird dann etwas mehr los sein, als in diesem Video. *g*)

Ja, wie du gesehen hast, wird es dynamisch. Das heißt, es werden zur Laufzeit neue Ansichten erzeugt, wobei jede einzelne Ansicht mithilfe des TFR Triplets den aktuell eingegebenen Text erhält. Außerdem - und das war damals im Prinzip der eigentliche Grund für dieses kleine Programm - bekommt das Hauptformular die verschiedenen Ansichten auf die selbe Weise.

Ich würde sagen, wir fangen mit dem einfachsten Teil der Software an. Das wäre in diesem Fall die Benutzeroberfläche, bzw. dessen Innenleben ... also der Quellcode.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace PFDynamic
{
 public partial class Form1 :
  Form,
  IAmTextTransmitter,
  IAmAddTextViewCaller,
  IAmTextViewReceiver
 {
  public Form1()
  {
   InitializeComponent();
  }

  public event EventHandler SendText;

  public string ReturnText()
  {
   return textBox1.Text;
  }

  private void textBox1_TextChanged(object sender, EventArgs e)
  {
   Say.Carefully(SendText, this);
  }

  private void button1_Click(object sender, EventArgs e)
  {
   Say.Carefully(AddTextView, this);
  }

  public event EventHandler AddTextView;

  public void ReceiveTextView(IAmTextView textView)
  {
   this.Controls.Add(textView.Control);
   Say.Carefully(SendText, this);
  }
 }
}

Das ist wirklich alles! Mehr manueller Quellcode steckt nicht in der Form1-Klasse.
Ich werde das Listing nun einzeln erklären.


Zeige mir deine implementierten Schnittstellen und ich sage dir, wer du bist!
Diese Aussage ist wörtlich zu verstehen. Sehen wir uns dazu den Klassenkopf an:

public partial class Form1 :
  Form,
  IAmTextTransmitter,
  IAmAddTextViewCaller,
  IAmTextViewReceiver

Ich weiß an dieser Stelle exakt 4 Dinge von Form1. Zum Einen ist sie von der Basisklasse "Form" abgeleitet. Das ist aber nichts Besonderes.
Viel interessanter sind die Schnittstellen! Diese kann ich direkt so lesen, als wären sie eine "Personenbeschreibung" von Form1.
"IAmTextTransmitter" bedeutet: "Ich bin ein Text-Transmitter." Somit weiß ich, dass Form1 aktiv einen Text versendet.
"IAmAddTextViewCaller" bedeutet: "Ich bin ein AddTextView-Caller." Somit weiß ich, dass Form1 sagt, dass eine Textansicht hinzugefügt werden soll.
"IAmTextViewReceiver" bedeutet: "Ich bin ein TextView-Receiver." Somit weiß ich, dass Form1 passiv eine Textansicht empfängt.

Ohne den eigentlichen Quelltext gesehen zu haben, weiß ich in dem Moment sofort, welche Rolle die Form1 in dem Gesamtsystem spielt. Das geht aber eben nur, wenn hier keine Willkürlichkeit herrscht.
Sehen wir uns dazu die Schnittstellen selbst einmal an. (Alle Schnittstellen sind natürlich im Namespace "PFDynamic" enthalten. Ich habe das nur zur Übersichtlichkeit weggelassen.)

public interface IAmTextTransmitter
{
 event EventHandler SendText;
 string ReturnText();
}

Zufällig passt diese Schnittstelle exakt zu dem theoretischen Beispiel meiner TFR Triplet Erklärung. ;-)
Man sieht hier, dass die Schnittstelle ein Event "SendText" und eine Methode "ReturnText()" fordert. Mehr nicht.
Die anderen beiden Schnittstellen sind genauso übersichtlich und erklären sich quasi selber, weshalb ich da nichts dazu sage.

public interface IAmAddTextViewCaller
{
 event EventHandler AddTextView;
}

public interface IAmTextViewReceiver
{
 void ReceiveTextView(IAmTextView textView);
}

Aber man sieht hier, dass die Schnittstellen nur das Nötigste enthalten. In der Regel sind es nicht mehr, als zwei Elemente. Das ermöglicht es, einer Klasse mal schnell eine Schnittstelle zuzuweisen und diese zu implementieren.
Kommen wir zum nächsten Punkt!

Mit der Idee fängt alles an!
So banal das klingt, ist es aber auch! Es ist ein bisschen sowas, wie "Programming by Wishful Thinking". Allerdings mit der totalen Entkopplung der Objekte zueinander.
Bezogen auf diese kleine Anwendung, dachte ich mir Folgendes:
"Wenn ich auf einen Button klicke, soll eine Ansicht hinzugefügt werden. In einer TextBox möchte ich Text eingeben können, der automatisch bei allen anderen Ansichten aktualisiert wird. Zudem möchte ich jede Ansicht individuell entfernen können."

So klickte ich mir zunächst die Oberfläche zusammen. Einen Button und eine TextBox. Die Ansichten ließ ich logischerweise weg, weil die ja dynamisch hinzugefügt werden sollten.
Ich klickte doppelt auf den Button, weil ich ja das Klick-Event haben wollte. Und was wollte ich mit dem Klick erreichen? Richtig, ich wollte eine Text-Ansicht hinzufügen. So programmierte ich es dann auch:

private void button1_Click(object sender, EventArgs e)
{
 Say.Carefully(AddTextView, this);
}

Der Aufruf mit dem "Say Carefully" ist nur ein einfacher Wrapper, um mir Arbeit abzunehmen und die Lesbarkeit zu erhöhen. So sieht diese Wrapper-Klasse aus:

using System;

static class Say
{ 
 public static void Carefully(EventHandler statement, object caller)
 {
  if (statement != null)
   statement(caller, EventArgs.Empty);
 }

 public static void Carefully(EventHandler statement, object caller, EventArgs arguments)
 {
  if (statement != null)
   statement(caller, arguments);
 }
}

Diese Hilfsklasse verwende ich in allen MPT-Projekten. Das aber nur am Rande.
Wie man in dem Beispiel gesehen hat, formuliere ich bei dem Button-Klick im Grunde nur das, was ich damit erreichen will. Um das "WIE" habe ich mir in dem Moment noch keine Gedanken gemacht, weil dies nur die Gedanken blockieren würde. Durch den Button-Klick soll eine Ansicht hinzugefügt werden. Punkt! Mehr ist an dieser Stelle nicht wichtig. Dann hatte ich die jeweilige Caller-Schnittstelle dazu erstellt. (Siehe oben!)

Die nächste Sache: So wie ich Text in die TextBox eingebe oder verändere, soll dieser an alle Empfänger gesendet werden.
Also nahm ich hierfür einfach das "TextChanged" Ereignis der TextBox her:

private void textBox1_TextChanged(object sender, EventArgs e)
{
 Say.Carefully(SendText, this);
}

Das sollte sich auch wieder selbst erklären, denke ich. Das macht eben einen weiteren großen Vorteil aus, dass man den Quelltext nicht extra kommentieren braucht. Der Quellcode ist selbstredend. So sollte das IMHO auch sein. Wer Kommentare braucht, hat IMHO eine unsaubere Codebase und sollte an der Verständlichkeit seines Quellcodes arbeiten.

So, und wer Text sendet, muss diesen auch bereitstellen! Dafür gibt es ja diese Funktion:

public string ReturnText()
{
 return textBox1.Text;
}

Hier wird eben einfach der Text in der TextBox zurückgegeben. Einfacher geht es nicht.

Eine letzte Sache fehlt noch: Wir wollten ja auch Text-Views empfangen. Kommt also nun zum Schluss diese Möglichkeit hinzu:

public void ReceiveTextView(IAmTextView textView)
{
 this.Controls.Add(textView.Control);
 Say.Carefully(SendText, this);
}

Auch diese Methode kam quasi so zustande, wie ich sie gern verwenden wollte. Wir erinnern uns: Wir wollten alles nur über Schnittstellen abwickeln!
So wollte ich als Parameter eine Schnittstelle, die sich selber als "IAmTextView" bezeichnet. Und diese dient quasi als Wrapper für das "Control", welches ich ja in der ersten Anweisung der "Controls"-Liste der Form-Klasse hinzufüge. Sonst wäre dies nicht Bestandteil der Form und somit nicht sichtbar.
Danach möchte ich, dass nochmal der Text gesendet wird. Denn die neu hinzugefügten Ansichten sind ja praktisch noch jungfräulich ... also leer.

Das ist auch ein wichtiger Punkt! Ich hätte das Senden des Textes auch in das Button-Klick Event einfügen können, z.B. so:

private void button1_Click(object sender, EventArgs e)
{
 Say.Carefully(AddTextView, this);
 Say.Carefully(SendText, this);
}

Die Software hätte genauso funktioniert, da dies quasi serielle Funktionsaufrufe sind. Was wäre aber, wenn das Event "AddTextView" gar nicht bedient wird? Dann wird "SendText" umsonst aufgerufen, weil ja keine Ansicht hinzugefügt wurde und alle bestehenden Ansichten bereits aktualisiert sind. Ein unnötiger Funktionsaufruf, also. Bei diesem Beispiel fällt das ja nicht ins Gewicht. In der Praxis könnten hier aber komplexe Operationen dahinter stehen, was dann sehr wohl stören könnte.

Oder ein anderes Beispiel:
Nach dem Aufruf von "AddTextView" wird die Verarbeitung an einen Thread übergeben, wodurch die Funktion sofort wieder zurück kommt und das Event "SendText" aufruft. Der andere Thread ist aber noch dabei, die "TextView" zu generieren. (Das kann ja in der Praxis auch sehr komplex sein.) Nun wurde "SendText" aber schon ausgeführt, obwohl noch keine neue "TextView" erzeugt wurde. Kurze Zeit später ist der Thread soweit und wir bekommen die "TextView". Aber sie wird keine Daten enthalten, weil der Zug (SendText) schon abgefahren ist. Dumm gelaufen. (Aber keine Angst! Der Kunde wird sich schon über den Bug beschweren. *g*)

Daher sollte man sich immer folgende Frage stellen:
"Sind die Ereignisse abhängig oder unabhängig voneinander?"
Am besten versucht man sich dabei immer das Thread-Beispiel vorzustellen. Also das Szenario, dass die Events quasi parallel ausgeführt werden.
Sind es parallele Prozesse, kann man diese auch parallel aufrufen bzw. implementieren.
Müssen die Events seriell, also nacheinander ausgeführt werden, sollten diese auch so aufgerufen werden.

Das soll es soweit erstmal gewesen sein. Im zweiten Teil geht es dann weiter. (Ich schätze mal, dass ich dann die "Mittelmänner" zeige.)

Sonntag, 18. April 2010

Hypothese: "Die Micro-Pattern-Trilogie reicht völlig!"

Ehe wir uns die praktische Anwendung der Micro-Pattern-Trilogie (kurz MPT) ansehen, möchte ich hier zunächst eine kühne Behauptung aufstellen:

"Ich behaupte, dass man nur unter Zuhilfenahme der MPT absolut jede denkbare Software realisieren kann, egal wie komplex diese ist!"

Ziemlich gewagt, diese Aussage, oder? Gerade, wenn man bedenkt, dass es schon andere helle Köpfe gab, die sich viele Entwurfsmuster ausgedacht haben, um den heutigen Softwareproblemen Herr zu werden.
Ich sage auch nicht, dass alle Muster sinnlos sind. Im Gegenteil!
Es gibt aber eben auch Muster, welche IMHO redundant sind. Zum Beispiel gibt es da einmal den "Model View Controller" und den "Model View Presenter". Beide erfüllen unter'm Strich die selbe Funktion: Das Eine soll vom Anderen getrennt sein und dennoch hängen alle drei Elemente zusammen. Beim MVC sieht man diesen Widerspruch sehr schön.
Warum machen sich die Entwickler also das Leben so schwer?
Vom MVC gibt es zig Modifikationen, sodass man nicht sagen kann: "Ach ja, MVC! Deine Software funktioniert also so und so!" Der andere guckt dich dann verdutzt an und sagt: "Ähm ... nee, meine MVC-Variante funktioniert so und so!" ... Ach so, verstehe ... oder auch nicht. Gut, dass wir drüber geredet haben. ;-P

Bei der MPT sieht es aber anders aus! Hier ist die Spezifikation total simpel und eindeutig. Wenn ich von einem Caller-Mediator-Listener Triplet (kurz CML) rede, dann funktioniert es auch genau SO und nicht anders. Hier wird es keine Abwandlungen geben! (Ein Wassermolekül besteht schließlich auch immer aus zwei Wasserstoffatomen und einem Sauerstoffatom. Und wenn man von Wasser redet, weiß jeder, was das ist.)

Wenn ich mir überlege, dass es zu diesen diversen Pattern ganze Bücher gibt, welche diese erklären ... OMG!
Und manche Pattern sind auch nicht gerade einfach zu verstehen. (Wie will man dann erst Quellcode verstehen, welcher mit so einem Pattern realisiert ist?)

Sicher, MPT ist auch ein Muster. Aber ein sehr Einfaches, was letztlich jeder Mensch nachvollziehen kann.
Die MPT besteht nur aus drei einfachen Triplets:

Die Vorteile überwiegen die Nachteile um ein Vielfaches:

Totale Entkopplung der einzelnen Objekte!
Kein Objekt weiß von der Existenz eines anderen Objekts. Die einzelnen Objekte dürfen auch gar nichts voneinander wissen, geschweige denn direkt miteinander kommunizieren. Dies würde unweigerlich zu einer chaotischen Programmstruktur führen. Theoretisch sollte jedes Objekt völlig ohne Konstruktor-Argumente auskommen. Was ein Objekt benötigt, kommuniziert es einfach "ins Blaue" hinein. Irgendjemand wird "ihm" schon geben, was "er" verlangt (siehe CPS). Woher "er" es letztlich hat, interessiert "ihn" nicht. Die Mittelmänner (Mediator, Frequency, Provider) zählen hier ausdrücklich nicht als Objekte im oben genannten Sinne. Sie sind einfach notwendig, um die Objekte zu verbinden und dadurch Funktionalität zu gewährleisten. Man referenziert sie auch nicht, sondern sie sind einfach da.

Objekte kommunizieren vorsichtig bzw. passiv!
Das heißt, Objekte können zwar sagen, was sie wollen. Ihrem Wunsch muss aber nicht nachgegangen werden. Dadurch kann man auch mal temporär einen Kommunikationskanal unterbrechen, wenn man z.B. logische Fehler im Programm aufspüren will. Im besten Fall funktioniert dann eine Funktion nicht mehr, löst aber keine Exception aus. (Zwei Wassermoleküle kann man auch trennen, ohne dass die Welt untergeht. Bei der Software sollte es IMHO auch so sein.)

Man kann agil und flexibel seine Software entwickeln!
Schon lange vorbei sind die Zeiten, wo man vorher erstmal großartig seine Software plant. (Und das möglichst noch mit UML.) Man entwirft höchstens im Kopf mal ein grobes Konzept, wie das Gesamtbild aussehen soll.
Software darf man IMHO nicht mit einem statischen Gebäude vergleichen. Software ändert sich ständig! Da kann ich wirklich ein Lied von singen. Besonders, wenn eine recht komplexe Änderung in recht kurzer Zeit umgesetzt sein soll. (Sehr lustig wird es, wenn dann plötzlich Probleme auftreten, mit denen man gar nicht gerechnet hat.)
Mit der MPT kann man quasi die Software so entwickeln, wie sie einem der Kunde von der Funktionalität her erklärt. Einen "Button-Klick" in der Story kann man z.B. sofort mit dem CML verknüpfen, da dieser Klick irgendwas auslösen soll. Wie es weiter geht? Das kann an dieser Stelle sogar offen bleiben. Es ist wie bei einem Puzzle, wo jenes CML Triplet einen kleinen Teil vom Ganzen ausmacht. Man platziert es erstmal irgendwo und nimmt sich das nächste Puzzle-Teil her. Dies kann z.B. ein völlig anderer Aspekt sein, der mit dem "Button-Klick" gar nix zu tun hat.
Diese Freiheit hat man nur deshalb, weil alle Triplets atomar und völlig unabhängig voneinander sind. Am Ende hat man aber ein "fertiges" Puzzle bzw. eine funktionale Software, welche das tut, was sie soll.

Durch Schnittstellen werden die Dinge miteinander verknüpft!
Das ist sogar ganz wichtig, bei diesem Konzept! Die Mittelmänner erwarten ausschließlich Schnittstellen als Konstruktor-Argument! Niemals eine konkrete Klasse! Dies wäre ein fataler Bruch bei der MPT und die Flexibilität wäre dahin.
Vielmehr sollte man den Vorteil darin sehen, dass eine Klasse beliebig viele Schnittstellen implementieren kann. (Zu diesem Thema wird es auch noch einen extra Beitrag geben, weil die Schnittstellen so wichtig sind.)

So kurze Datenwege, wie möglich!
Theoretisch dürften hier die transportierten Daten (siehe TFR und CPS) auf kürzestem Wege beim Ziel ankommen, weil das jeweilige Objekt direkt sagt, was es benötigt. (z.B. beim CPS)
Somit könnte das angeforderte "Ding" direkt übermittelt werden, ohne erst über zig Ecken wandern zu müssen. Wie gesagt, theoretisch! ;-)


So, das wären spontan die Vorteile. Das ganze hat leider auch einen offensichtlichen Nachteil:

SEHR AUFWÄNDIG!
Für jedes Triplet muss man zwei Schnittstellen anlegen und den Mittelmann vollständig implementieren.
Zudem werden da auch viele einzelne kleine Quellcodedateien anfallen.
Ja, das ödet wirklich sehr an und wäre zurecht ein Grund zu sagen: "OMG, da bleibe ich lieber bei quick & dirty!"
Da bleibt momentan nix anderes übrig, als in den sauren Apfel zu beißen oder es zu lassen.
(Ich habe da aber schon ein privates Projekt im Sinn, welches eine Art RAD-Tool werden soll, um mir diese Arbeit abzunehmen. Natürlich möchte ich dieses Tool konsequent mit der MPT ins Leben rufen. Das wäre dann auch gleich die beste Erprobung für die Praxistauglichkeit der MPT.)

Soviel dazu. Bis zum nächsten Artikel! :-)

"Client - Provider - Server" im Detail

Im letzten Beitrag habe ich erklärt, wie das Transmitter-Frequency-Receiver Triplet (kurz TFR) funktioniert.

Nun ist das letzte Triplet der "Micro-Pattern-Trilogie" dran: das Client-Provider-Server Triplet (kurz CPS).

Wie kam ich auf das CPS Triplet? Das ergab sich aus der Tatsache, dass es neben einer passiven Datenübertragung auch eine Aktive geben muss. Einerseits würde das Konzept, ohne die Möglichkeit der Datenübertragung auf Abruf, unvollständig erscheinen. Zum Anderen ist dies sogar unabdingbar, wenn man konsequent ausschließlich mit dieser "Micro-Pattern-Trilogie" seine Software entwickelt. Was ich damit meine, wird in den zukünftigen Artikeln ersichtlicher werden. Es sei nur kurz erwähnt, dass es damit zu tun hat, wie man derartige Software zu entwickeln beginnt. (Das werde ich aber in anderen Beiträgen ausführlich erklären und würde hier zu weit führen.)

Bei der Namensfindung orientierte ich mich auch wieder am realen Beispiel:
Ich (der Client) suche etwas bei Google (der Server). Dabei wandern meine Suchanfragen und meine persönlichen Suchergebnisse von Google über den Provider.

Damit sollte die Sache eigentlich schon klar sein.
Hier nun die Präsentation:



So, nun sind alle Triplets in ihrer Funktionsweise erklärt und wir tasten uns langsam aber sicher zu den praktischen Beispielen vor.

Samstag, 17. April 2010

"Transmitter - Frequency - Receiver" im Detail

Im letzten Beitrag habe ich erklärt, wie das Caller-Mediator-Listener Triplet (kurz CML) funktioniert.

Nun möchte ich erklären, wie das Transmitter-Frequency-Receiver Triplet (kurz TFR) funktioniert.
Wie bin ich überhaupt darauf gekommen? Der Gedankengang war im Grunde recht einfach. Da das CML Triplet keine Daten übertragen darf, musste etwas her, was dies ausdrücklich spezifiziert.
Ich dachte zuerst an eine Möglichkeit, Daten passiv empfangen zu können. Als Beispiel sei hier die Statusleiste genannt, welche bei fast allen GUI-Anwendungen existiert. Diese Statusleiste fordert keine Daten an, sondern empfängt diese automatisch, wenn sich der Status geändert hat. (Meist ist dies ja ein einfacher String.)

Oder ein anderes Beispiel:
Der Benutzer gibt z.B. in einer TextBox beliebigen Text ein, welcher gleichzeitig in mehreren Ansichten aktualisiert werden soll.

Mit diesem Beispiel kam mir letztlich die Idee:
Es gibt einen Sender, welcher Daten aussendet. Gleichzeitig kann es mehrere Empfänger geben, welche diese Daten empfangen.
So etwas kennt jeder aus dem realen Leben: Fernsehsatelliten senden ein Fernsehsignal in Richtung Erde und diverse Sat-Empfänger können dieses Signal empfangen.
Es war sofort klar, dass die Schnittstelle für den Empfang schlicht Receiver heißen musste.
Die Schnittstelle für den Sender wollte ich auch erst "Sender" nennen, jedoch gab es diese Bezeichnung schon im .NET Framework. Außerdem klang es nicht so "cool".
Einmal kurz gegoogelt und ich fand den Begriff Transmitter. Dieser passt auch besser zum Rundfunk-Beispiel.
Nun brauchte ich noch einen geeigneten Name für den Mittelmann.
Was verbindet beide Dinge miteinander? Wirklich spontan fiel mir sofort der Äther ein. Ich gebrauchte natürlich den englischen Begriff Aether. Tatsächlich blieb ich für einige kurze Experimente bei diesem Begriff, aber mich störte er immer mehr, weil man ihn heute im Grunde nur noch in der Esoterik antrifft.
Ich überlegte und überlegte, aber mir wollte keine Alternative einfallen. Dann nahm ich mir in Gedanken nochmal den Vergleich mit dem Rundfunk her. Was geschieht also, wenn man z.B. beim Sat-Receiver oder beim Radio den Sender wechselt? Klar, man ändert die Frequenz. Bingo! Das war der gesuchte Begriff! Und er passte auch genau zu dem Presenter-First Konzept. Auch hier entschied ich mich letztlich für die englische Bezeichnung: Frequency.

Nach der langen Einleitung kommt nun die versprochene Präsentation:


So, dieses war der zweite Streich und der Dritte folgt sogleich!

(Mal sehen, ob ich es bis morgen Abend schaffe. Dann hätten wir die graue Theorie hinter uns und es folgen dann endlich praktische Beispiele und Überlegungen für den praktischen Einsatz.)

Donnerstag, 25. März 2010

"Caller - Mediator - Listener" im Detail

In meinem ersten Beitrag hatte ich die Idee des "Caller - Mediator - Listener" Triplets kurz erklärt. Nun möchte ich dieses im Detail erläutern und erklären, was ich mir dabei gedacht habe.

Der Grundgedanke, den ich hatte, war letztlich gar nix weltbewegend Neues.
Ich habe einfach den Sachverhalt der Objektkommunikation aus der objektorientierten Programmierung wörtlich genommen.
Somit gibt es eben:
  • einen Caller, der etwas zu sagen hat
  • einen Mediator, der die Botschaft vom Caller empfängt und weiterleitet
  • einen Listener, der letztlich die Nachricht empfängt und die eigentlichen Befehle durchführt
Damit das ganze noch anschaulicher wird, habe ich eine Präsentation erstellt. Ich hoffe, es ist anschaulich genug.
(Da es sich aber hierbei um eines der einfachsten der insgesamt 3 Triplets handelt, sehe ich da keine Probleme.)


So, die nächsten beiden Triplets werden auf jeden Fall interessanter, denn da werden Daten übertragen! Da gibt es dann auch schönere Anwendungsbeispiele.
Als Vorlage werde ich gleich wieder diese Präsentation verwenden, dann bereitet es nicht ganz so viel Arbeit.

PS.:
Alle Dateien, die hinsichtlich dieses Blogs anfallen, findet man auch in einem öffentlichen Ordner: KLICK

Montag, 22. März 2010

Es dauert noch etwas ...

Eigentlich wollte ich die 3 Triplets heute in einem Beitrag in Textform erklären.
Mir kam aber eine bessere Idee in den Sinn!
Da Bilder bekanntlich mehr sagen, als 1000 Worte, beschloss ich diese Triplets mithilfe einer Präsentation zu beschreiben.
Dazu verwende ich Google Text & Tabellen.
Es funktioniert bis jetzt alles wunderbar und auch die grafischen Funktionen machen keine Probleme.

Man darf sich also auf 3 anschauliche Präsentationen freuen. (Zumindest hoffe ich, dass sie anschaulich genug sein werden. *g*)

Das ganze ist jedoch etwas aufwändig. Aber sobald die erste Präsentation fertig ist, werde ich sie hier im Blog veröffentlichen. Bis dahin bitte ich um etwas Geduld! :-)

Sonntag, 21. März 2010

Presenter First - Geniale Idee!

Meinen ersten Beitrag beginne ich sogleich mit einem Konzept, welches sich "Presenter First" nennt. Ich möchte hier nicht die Grundlagen dessen ausschweifend erklären.
Hierzu verweise ich lieber auf zwei Dokumente von den eigentlichen "Erfindern".

Da wäre zunächst das eigentliche Paper, welches die Sache konzeptionell erklärt:
Presenter First: Organizing Complex GUI Applications for Test-Driven Development [pdf] [doc01]

Und dann noch ein eher praktisches Beispiel, welches in dem Magazin "Better Software" erschien:
Big, Complex, and Tested? Just Say ‘When’ [pdf] [doc02]

Nun, als ich das erste mal von diesem Konzept hörte, war ich von dieser Idee total angetan.[1]
Man stellt sich dann aber dennoch die Frage: "Wo fange ich da nun am besten an, meine Software zu entwerfen?"
Klar, bei dem gewünschten Feature. Das ist ja der Sinn von dem Konzept.
Das mag auch noch einfach sein, wenn das Feature in etwa so aussieht: "Wenn ich auf den Button [Beenden] klicke, soll die Anwendung beendet werden."
Das ist auch soweit einfach, wenn hierbei wirklich nur die Anwendung beendet werden soll und sonst nix weiter.
Was aber, wenn mit diesem einfachen Feature komplexe Aufräumarbeiten verbunden sind? Vielleicht soll der Benutzer noch gefragt werden, ob sein geändertes Dokument vorher gespeichert werden soll? Etc.
Da ist das zuerst genannte Feature die höchste Abstraktionsebene.
Aus der "Presenter First"-Sicht ist das im Grunde auch so richtig. Was mit dem Beenden-Vorgang sonst noch zusammen hängt, interessiert in dieser Abstraktionsebene überhaupt nicht. Das ist ja auch Sinn und Zweck dieser strikten Entkopplung.
Es stellt sich daher die Frage: "Wie tief und fein sollte die Abstraktion sein? Das heißt, erstelle ich sehr viele kleine Sub-Presenter, oder sollte das an der Stelle doch etwas grober ausfallen?"

Desweiteren stand ich schon vor folgendem Problem:
Sollte zu jedem Presenter auch nur ein Fenster/Dialog gehören? Oder sollte jeder Button/jede Komponente einen eigenen Presenter bekommen?
Ersteres macht die ganze Sache sehr einfach und übersichtlich, ist aber sehr unflexibel und problematisch, wenn man z.B. noch einen Adapter oder generell noch weitere Abstraktionsebenen zwischen eine einzelne Komponente des User Interfaces schalten will.
Bei der anderen Variante gäbe es nicht dieses Problem. Aber hier hat man am Ende Unmengen einzelner Schnittstellen-, Presenter-, Model- und View-Klassen.

Nun, die Erfinder meinen hier, man solle logisch zusammengehörige Komponenten gemeinsam mit einem Presenter verbinden.
Klar, das ist erstmal ein guter Kompromiss. Da könnte man die Komponente, welche noch weitere Adapter braucht, entsprechend separieren und die anderen Komponenten zusammen halten.
Allerdings ist mir das zu willkürlich.

Und das ist gleich das Nächste. Die Willkürlichkeit, wie die Model-View-Presenter-Einheiten zusammengesetzt sind. Das heißt, bei dem originalen Konzept sieht man nicht direkt von Außen, wie der Daten- und Nachrichtenfluss verläuft. Das heißt, jemand, der den Quellcode zum ersten Mal sieht, wird nicht auf Anhieb wissen, was dieses Model oder jene View macht oder welche Rolle der Presenter dabei spielt.
Dazu müsste man sich nämlich erst tiefer in den Quellcode einlesen.

Ich grübelte noch einige Zeit über die oben genannten Probleme. Immer wieder las ich mir die Dokumente durch.
Irgendwann ließ ich mir folgenden Text [doc02; letzte Seite unten] so richtig auf der Zunge zergehen:
The “micro-architecture” of Presenter First is like a carbohydrate chain in organic chemistry — interesting functionality arises from applying the same basic pattern of object composition. The mechanisms and domain may vary, but the core principles do not: When the customer says “when,” write it into a presenter.
Dann war es mir klar. Was dieses an sich tolle Konzept brauchte, war eine gewisse Ordnung in dem willkürlichen Chaos. So, wie auch die Moleküle oder die Zellen des Menschen nach einer gewissen Ordnung funktionieren.
Ich übertrug den Ordnungsbegriff auf die Softwareentwicklung und kam dann zu folgender Lösung:

Jedes Objekt übernimmt einen sehr kleinen Aufgabenbereich in dem Gesamtsystem. Ein Button im UI wird höchstens nur als Auslöser für eine komplexe Aufgabe dienen. Vielleicht empfängt er auch mal Daten in Form einer neuen Beschriftung oder anderer Eigenschaftswerte. Aber in erster Linie wird er nur zum Triggern da sein.

Somit übernimmt der Button eine entscheidende Aufgabe: Er ist ein Caller!
Aber ein Caller alleine bringt leider nicht viel. Es muss auch jemand auf das bestimmte Signal hören, was der Caller ausgesandt hat. Also muss es noch einen Listener geben.
Aber da fehlt noch einer! Caller und Listener sollen ja nix voneinander wissen. Ein Presenter muss immer existieren, der beide miteinander verbindet. Aber die Bezeichnung "Presenter" passt hier nicht gut. Zwischen einem "Rufer" und einem "Zuhörer" muss noch jemand vermitteln. Dies wäre im klassischen Sinne die "Luft". "Air" klingt aber komisch, weshalb ich mich hier für Mediator entschieden habe.

Das ist nun also die erste Triplet-Gruppe von insgesamt 3 Gruppen, die ich mir ausgedacht habe, um ein bisschen Ordnung und Struktur in die Sache zu bekommen.

Caller - Mediator - Listener

Die anderen Beiden nenne ich jetzt erstmal so, ohne Erläuterung.

Transmitter - Frequency - Receiver
und
Client - Provider - Server

In den nächsten Beiträgen werde ich diese 3 Triplets genauer beleuchten.
(Theoretisch sollte sich schon die Funktion der letzten 2 Triplets anhand der Bezeichnungen erahnen lassen. *g*)

Das soll es aber erstmal für heute gewesen sein.


  1. Und ich bin noch immer davon begeistert. Doch ich habe noch so ein paar Schwierigkeiten damit, weshalb ich dieses Konzept noch nicht auf Arbeit verwende. Das werde ich aber in diesem und weiteren Blogbeiträgen nach und nach erläutern.