Dieses Blog durchsuchen

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

Keine Kommentare:

Kommentar veröffentlichen