Dieses Blog durchsuchen

Posts mit dem Label 1. MPT Beispiel werden angezeigt. Alle Posts anzeigen
Posts mit dem Label 1. MPT Beispiel werden angezeigt. Alle Posts anzeigen

Dienstag, 11. Mai 2010

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, 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.)