Laden...

EBC versus Databinding

Erstellt von ErfinderDesRades vor 13 Jahren Letzter Beitrag vor 13 Jahren 1.932 Views
ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 13 Jahren
EBC versus Databinding

Gibts eigentlich schon iwo eine EBC-Demo-Anwendung, die auch läuft?

Mich plagt nämlich ein bischen der Verdacht, dass Databinding ein evtl. vergleichbares Potential hat.
Und ich könnte gucken, ob ichs mit Databinding ebenso hinbrächte, evtl. mit weniger Overhead.

Databindable Klassen sind ja auch event based components, nur mit (sehr!) anderer Verknüpfungs-Konvention.
Und Databinding ist nicht unbedingt auf Controls beschränkt (gugge etwa Databinding für nicht-Controls)
Und man hat auch eine Unabhängigkeit der Komponenten, und die Notwendigkeit, sie zusammenzustöpseln.

kurze Nebenfrage: Die Konzeptions-weise mit den Bausteinen und Platinen findich eiglich ganz einleuchtend (UML habich noch nie verstanden), und die ist auch gar nicht unbedingt an EBC gebunden, scheint mir - odr?

Der frühe Apfel fängt den Wurm.

71 Beiträge seit 2010
vor 13 Jahren

Probiers doch aus mit Databinding.
Aber nicht überall da, wo Event drauf steht, ist auch vergleichbarer Vorteil drin.

Meine These: Databinding ist genau das. Databinding. Fertig. Das hat seinen Zweck. Aber das ist kein Konzept, um Prozesse zu koppeln.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ErfinderDesRades,

DataBinding zwischen Komponenten ist auf jeden Fall eine nette Idee!

Jedoch koppelt das ja nur Daten, nicht die Verarbeitung. Natürlich kann man eine gewisse Verarbeitung über die converter-Delegaten durchführen, aber das trägt sicher nicht sehr weit, bzw. wäre ein Overkill, wenn man die ganze Verbreitung darüber erledigen wollte.

Insofern denke ich, dass DataBinding zwischen Komponenten kein Ersatz bzw. keine Alternative zu EBCs ist.

Die Konzeptions-weise mit den Bausteinen und Platinen findich eiglich ganz einleuchtend (UML habich noch nie verstanden), und die ist auch gar nicht unbedingt an EBC gebunden, scheint mir - odr?

Ja, so sehe ich das auch.

herbivore

ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 13 Jahren

Probiers doch aus mit Databinding.

Ja, das war ja meine Eingangsfrage, ob ich iwo eine EBC-Demo-App downloaden kann, und gucken, ob ich das mit Databinding nachgebastelt kriege.

Der frühe Apfel fängt den Wurm.

71 Beiträge seit 2010
vor 13 Jahren

hier hab ich ne Anwendung mit EBC gebastelt

ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 13 Jahren

hmm, einen download gibts da nicht, oder wird der erst sichtbar, wenn man sich angemeldet hat? nee, auch nicht.

Der frühe Apfel fängt den Wurm.

71 Beiträge seit 2010
vor 13 Jahren

das ist ein repository. da kannst du die quellen auschecken. mit mercurial.

U
208 Beiträge seit 2008
vor 13 Jahren

und ich hab mich gefragt, wieso ich mit tortoise svn so seltsame fehlermeldungen bekomme... mercurial war das stichwort. danke!

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo Ralf,

ist In_XXX und Out_XXX deine neue Konvention für die In-/Output-Pins? Finde ich gut, da sofort klar ist wohin die Nachrichten gehen.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

71 Beiträge seit 2010
vor 13 Jahren

im augenblick schreib ich die pins meist mit In_... und Out_...
ich habs mal zeitweise anders gemacht, doch irgendwie ist das haften geblieben.

3.728 Beiträge seit 2005
vor 13 Jahren
Benennungskonventionen

Hallo Ralf,

In_ und Out_ finde ich sehr gut. Ich habe mir schon eine Weile beim Benennen der Pins einen abgebrochen. Jeder Pin hatte ein anderes Verb (z.B. send, notify, receive). Dabei liegt in und out so nahe. Ich mag zwar keine Unterstriche, aber in dem Fall kann ich das unter Event-Style á la Button1_Click verbuchen.

Pin-Benennungsproblem gelöst.

ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 13 Jahren

So, heute habichmich da mal dran verkünstelt. Zuerst war ich ja geschockt - 10 Solutions für eine Konsole-Meldung: "Der Hund bellt um Mitternacht" - du lieber Gott! - gehts nicht noch komplizierter? 😉
Ist das komfortabler, wenn man mit Repositories arbeitet? Weil so findich das voll umständlich, bei jeder Änderung an einer Solution diese erstellen, die DLL suchen und in die Host-Solution kopieren, und dann die Host laufen lassen - bäh!

Meine erste untat war also, alle Projekte in eine Solution zu machen, dann läufts bei F5. Ist immer noch irre genug - 10 Projekte für eine....

Ich hoffe, als Trennung von Komponenten reicht das erstmal, wenn jede CodeDatei ein eigenes Projekt hat.

Mit meim Databinding binnichnich weiter gekommen. Ist zwar möööglich, bringt aber nur Umständlichkeiten und Risiken rein. Ehe man eine Property Databindable gemacht und verknüppert hat, hat man wesentlich einfacher die EBC-Events direkt verknüppert.

Was mir nicht so gefiel waren der MessageDecomposer und -Multiplier, und was damit im DomainBoard angestellt wurde:


   public class DomainLogicBoard : IDomainLogicBoard {

      public event Action<string> Out_TransformedLine = delegate { };

      private readonly Action<FileTransformationRequest> in_requestDecomposer;
      private readonly Action<string> in_configMultiplier;

      public DomainLogicBoard(IFileReader reader, IFileWriter writer, ILineTransformator trans) {
         var requestDecomposer = new MessageDecomposer<FileTransformationRequest, string, string>(
           (req, out0, out1) => {
              out0(req.OutputFilename);
              out1(req.InputFilename);
           });
         requestDecomposer.Out0 += writer.In_WriteFile;
         requestDecomposer.Out1 += reader.In_ReadFile;
         this.in_requestDecomposer = requestDecomposer.In_Decompose;
         var lineMultiplier = new MessageMultiplier<string>(2);
         lineMultiplier.OutputPins[0] = writer.In_WriteLine;
         lineMultiplier.OutputPins[1] = line => this.Out_TransformedLine(line);
         var configMultiplier = new MessageMultiplier<string>(2);
         configMultiplier.OutputPins[0] = writer.In_Configure;
         configMultiplier.OutputPins[1] = reader.In_Configure;
         this.in_configMultiplier = configMultiplier.In_Multiply;
         reader.Out_Line += trans.In_TransformLine;
         trans.Out_TransformedLine += lineMultiplier.In_Multiply;
      }

      public void In_TransformToFile(FileTransformationRequest request) {
         this.in_requestDecomposer(request);
      }
      
      public void In_ConfigureDefaultPath(string defaultPath) {
         this.in_configMultiplier(defaultPath);
      }

   }

(zoom in: )


         var requestDecomposer = new MessageDecomposer<FileTransformationRequest, string, string>(
           (req, out0, out1) => {
              out0(req.OutputFilename);
              out1(req.InputFilename);
           });
         requestDecomposer.Out0 += writer.In_WriteFile;
         requestDecomposer.Out1 += reader.In_ReadFile;
         this.in_requestDecomposer = requestDecomposer.In_Decompose;
   }

Da wird also ein requestDecomposer erstellt, dem ein nicht gleich verständlicher anonymer Delegat im Konstruktor übergeben werden muß. Dann wird an Out0 (was immer das sein mag) der WriteFile-Setter vom writer angehängt, und an Out1 der ReadFile-Setter vom reader angehängt.
Der Decompose-Setter von dem Ding wird als KlassenVariable gespeichert.

... lange denk ...

ah, er will erreichen, dass, wenn ein request reinkommt, dass req.OutputFile an writer.Outputfile geht, und req.InputFile an reader.Inputfile!

Kann man das nicht gleich hinschreiben?


         this.in_requestDecomposer = req => {
            writer.In_WriteFile(req.OutputFilename);
            reader.In_ReadFile(req.InputFilename);
         };

desgleichen mit dem MessageMultiplicator, dann kommt bei mir der Domain-Konstruktor zunächst mal so daher:


      public DomainLogicBoard(IFileReader reader, IFileWriter writer, ILineTransformator trans) {
         this.in_requestDecomposer = req => {
            writer.In_WriteFile(req.OutputFilename);
            reader.In_ReadFile(req.InputFilename);
         };
         this.in_configMultiplier = s => {
            writer.In_Configure(s);
            reader.In_Configure(s);
         };
         reader.Out_Line += trans.In_TransformLine;
         trans.Out_TransformedLine += writer.In_WriteLine;
         trans.Out_TransformedLine += s => this.Out_TransformedLine(s);
      }

Die letzten beiden Zeilen findich nochmal extra interessant, da werden an trans.Out_TransformedLine 2 Handler angehängt (statt eines MessageMultiplicator, an den dann 2 Handler gehängt werden) - sowas erwähntest du ja schon in einem deiner EBC-Artikel.

Und diese Figur


         trans.Out_TransformedLine += s => this.Out_TransformedLine(s);

entsteht wohl beim durchreichen eines Events - da wird quasi ein Out-Event ohne (bzw. mit einem anonymen) Zwischenschritt an ein anderes Out-Event gehängt, sodaß trans.TransformedLine die "Oberfläche" der DomainLogic erreicht.

Aber diese in_requestDecomposer und in_configMultiplier - Delegaten als Klassenmember fandich noch komisch, sodassich letztendlich auf


   public class DomainLogicBoard : IDomainLogicBoard {

      public event Action<string> Out_TransformedLine = delegate { };
      private readonly IFileReader reader;
      private readonly IFileWriter writer;

      public DomainLogicBoard(IFileReader reader, IFileWriter writer, ILineTransformator trans) {
         this.reader = reader; this.writer = writer;
         reader.Out_Line += trans.In_TransformLine;
         trans.Out_TransformedLine += writer.In_WriteLine;
         trans.Out_TransformedLine += s => this.Out_TransformedLine(s);
      }

      public void In_TransformToFile(FileTransformationRequest value) {
         writer.In_WriteFile(value.OutputFilename);
         reader.In_ReadFile(value.InputFilename);
      }

      public void In_ConfigureDefaultPath(string value) {
         writer.In_Configure(value);
         reader.In_Configure(value);
      }

   }

umgestellt hab.
Statt der Delegaten habichnun, ziemlich konventionell, die enthaltenen Komponenten als Klassenmember. Und statt im Konstruktor anonyme Methoden zu implementieren, die an Klassenmember-Delegaten zugewiesen werden, damit sie in In_TransformToFile() und In_ConfigureDefaultPath() aufgerufen werden können, lasse ich In_TransformToFile() und In_ConfigureDefaultPath() die Arbeit eben wirklich tun.
So hat man zwar nicht die gesamte Verdrahtung im Konstruktor, aber das kann man auch als Vorteil sehen, so Konstruktoren könnnen ziemlich fett werden (ich nehme an, ist dir nix neues).

Tja, zu guterLetzt habichnoch gehirnt, wie ich das wohl gemacht hätte, ohne Rücksicht auf iwelche Pattern, einfach, wie mir am günstigsten vorkommt. Ja, und da kam ich von deinem Platinen-Design eigentlich nicht wirklich weg.
Ich hab immerhin die DomainLogik als ein Klumpen implementiert, weil ich dachte "3 Pins muß eine Komponente schon verkraften". Der Rot13Transformer ist zu einer statischen Funktion mit Rückgabewert geschrumpelt, wenn man wollte, könnte man das in eine statische Klasse auslagern, dann wäre vom DomainBoard (nun ists ja kein Board mehr, sondern ein Baustein, und darf also komplizierter sein) nicht mehr soo viel übrig. Aber das mit den In-/Out- Pins - wie gesagt: was besseres ist mir auch nicht eingefallen, und ich hab mir Mühe gegeben 😉


   public class EDomainLogic : IDomainLogicBoard {

      private string defaultPath;
      private static readonly Encoding _Encoding = Encoding.Default;

      public event Action<string> Out_TransformedLine;

      public void In_TransformToFile(FileTransformationRequest request) {
         request.InputFilename = Path.Combine(defaultPath, request.InputFilename);
         request.OutputFilename = Path.Combine(defaultPath, request.OutputFilename);
         using(var sr = new StreamReader(request.InputFilename, _Encoding))
         using(var sw = new StreamWriter(request.OutputFilename, false, _Encoding)) {
            while(!sr.EndOfStream) {
               var s =TransformLine( sr.ReadLine());
               Out_TransformedLine(s);
               sw.WriteLine(s);
            }
         }
      }

      public void In_ConfigureDefaultPath(string defaultPath) {
         this.defaultPath = defaultPath;
      }

      private static string TransformLine(string value) {
         if(value == null) return null;
         var bytes = _Encoding.GetBytes(value);
         for(var i = bytes.Length; i-- > 0; ) bytes[i] = Rot13Byte(bytes[i]);
         return new string(_Encoding.GetChars(bytes));
      }

      private static byte Rot13Byte(byte charByte) {
         const byte A = 65;
         const byte Z = A + 25;
         const byte a = 97;
         const byte z = a + 25 ;
         if(IsBetween(A,charByte,Z)) return Rot13Byte(charByte, A);
         if(IsBetween(a, charByte, z)) return Rot13Byte(charByte, a);
         return charByte;
      }

      private static bool IsBetween(byte lower,byte b, byte upper) {
         return b >= lower && b <= upper;
      }

      private static byte Rot13Byte(byte b, byte letterBase) {
         return (byte)(letterBase + (((b - letterBase) + 13) % 26));
      }

   }

IDomainLogicBoard so gelassen, so konnte ich das direkt in Main() einbauen.
Insbes. habich IDomainLogicBoard gelassen, weil das FrontEnd getrennt bleiben sollte, weil das ist ein bischen sehr schlank, das lädt schon ein, da noch ein paar UserInput-Möglichkeiten dranzumachen.

spaßeshalber habichmal meine Werkstatt angehängt

Edit: ach, bei den In-Methoden (ich nenn die auch mal Setter, weil ich das Pärchen Out_Event - In_Methode quasi als Property auffasse) ist mir aufgefallen, da die ja nur einen Parameter haben, kann man den eigentlich immer mit "value" benamen, denn was für ein value das ist, wird ja schon durch den Namen der Methode ausgedrückt (auch eine Ähnlichkeit zu Properties). Ist vlt. für später von Belang, wenns mal Designer und generierten Code für EBC geben soll.

Der frühe Apfel fängt den Wurm.

742 Beiträge seit 2005
vor 13 Jahren

Ich würde dein DataBinding Konzept nicht aufgeben, es macht ja nichts wenn dafür ein kleines Framework benötigt wird.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ErfinderDesRades,

Mit meim Databinding binnichnich weiter gekommen. Ist zwar möööglich, bringt aber nur Umständlichkeiten und Risiken rein. Ehe man eine Property Databindable gemacht und verknüppert hat, hat man wesentlich einfacher die EBC-Events direkt verknüppert.

was demonstriert deine Anwendung denn dann? Wenn ich es richtig verstanden habe, wolltest du die EBC-Anwendung von Ralf mit DataBinding statt EBC umsetzen. Jetzt verwendest du - so wie ich es verstehe - doch kein DataBinding, sondern nimmst wie Ralf normales EBC. Was ist dann an einer Anwendung anders als an der von Ralf? Und was soll deine Anwendung im Vergleich mit der von Ralf zeigen?

Um alles besser zu verstehen, wäre außerdem die Frage wichtig, wie die Architektur deiner Anwendung aussieht?

Noch keine kleine Frage: DomainLogicBoard ist keine allgemeine Klasse für beliebige Domänenlogik, sondern sie dient konkret der Texttransformation, oder? Es wäre besser, wenn diese TextTransformationBoard o.ä. heißen würde, oder?

herbivore

ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 13 Jahren

achso, nee. Ich drücke eigentlich wortreich aus, dass EBC mich überzeugt hat, sonst nix 😉 .
Ich wollte gewissermaßen das Rad neu erfinden (EBC im allgemeinen, und die Rot13-App im besonderen), und habe gefunden, dasse jeweils ganz i.O. sind, die Räder.
Meine Änderungen halte ich im Grunde nicht für besonders gravierend, obs nun 10 Solutions in einem Repository sind, oder eine einzige, mit 10 Projekten. Oder ob man nun MessageDecomposer verwendet, oder das lieber dynamisch erledigt.
Ich könnte mir evtl. noch eine Variante ohne die ganzen Contract-Interfaces vorstellen, das ließe sich bei einem so kleinen Projekt glaub schneller entwickeln, aber es soll ja ein Modell für größere Projekte sein.
Selbst die Implementation des (ehem?) DomainlogikBoard als ein Baustein stelle ich mir vor, dass das als EBC-konform durchgeht.
Andererseits lege ich die Bastelei auch offen, um Möglichkeit zu geben, mir architektonische oder sonstige Mängel aufzuzeigen, die ich reingefrickelt habe.
An der Architektur ist nach meinem Verständnis nichts geändert.
Nur bei meinem Upload ist die DomainLogic-Platine in mehreren Variationen implementiert, und man kann ausprobieren, indem man im Main() betreffende Zeilen umkommentiert.

Edit: Die Namen habich so belassen, wie von Ralf vorgegeben - das macht meine Frickeleien leichter vergleichbar. Inhaltlich stimme ich zu, zumal mir der Begriff "Domäne" iwie nix sagt, da mussichmal recherchieren, was das eiglich bedeutet.

Der frühe Apfel fängt den Wurm.

71 Beiträge seit 2010
vor 13 Jahren

Cool, dass du dir die Mühe gemacht hast, ErfinderDesRades.

Dies hat mir einen guten Impuls gegeben:

So hat man zwar nicht die gesamte Verdrahtung im Konstruktor, aber das kann man auch als Vorteil sehen, so Konstruktoren könnnen ziemlich fett werden

Deine Vereinfachung des Ctor ist ja offensichtlich. Erst hab ich da gezuckt und gedacht, "Nein, nein, das lässt sich nicht mehr so einfach generieren."

Aber dann dachte ich: Es wird ja noch nichts generiert. Also muss man den Code auch nicht so schreiben, als würde er generiert. Nur wichtig zu wissen, wie er aussehen kann, um ihn generieren zu lassen.

Ich werde daher ab jetzt unterscheiden zw. Platinencode, an dem ich zeigen will, wie trivial-regelmäßig er aussehen kann, um ihn generieren zu können. Und Platinencode, den ich immer noch von Hand schreibe, bis es mal einen Generator gibt 😃

Solange von Hand geschriebener Code immer noch der grundlegenden SoC treu bleibt, d.h. in einer Platine nicht mehr tut, als zu verdrahten, dann ist alles gut. Ob die von-Hand-Verdrahtung da nur im Ctor sitzt oder nicht, ist egal.

Und solange ich noch von Hand verdrahte, erlaube ich mir auch den Verzicht auf ein paar Standardkomponenten wie den Multiplier oder Decomposer. Vielleicht notiere ich die noch im Schaltplan - aber in der Verdrahtung müssen sie nicht mehr unbedingt stehen. Das geht natürlich nur für so einfache Standardkomponenten.

ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 13 Jahren

@malignate:

Ich würde dein DataBinding Konzept nicht aufgeben, es macht ja nichts wenn dafür ein kleines Framework benötigt wird.

naja, EBC hat ja out-pins und in-pins, das sieht zB. beim Rot13-Transformator so aus:



      private readonly Encoding _Encoding = Encoding.Default;

      public void In_TransformLine(string line) {
         if(line != null) {
            var bytes = (from b in _Encoding.GetBytes(line) select Rot13Byte(b)).ToArray();
            this.Out_TransformedLine(new string(_Encoding.GetChars(bytes)));
         } else
            this.Out_TransformedLine(null);
      }

      public event Action<string> Out_TransformedLine;

kommt eine line rein, geht per this.Out_TransformedLine(newLine) eine andere heraus.

Als Property abgebildet wäre In_TransformLine eine writeOnly Property (es gibt nur den Setter), und Out_TransformedLine eine (andere) ReadOnly Prop, die aber NotifyChanged auslöst:



      private readonly Encoding _Encoding = Encoding.Default;
      private string _TransformedLine;

      public string In_TransformLine {
         set {
            if(value != null) {
               var bytes = (from b in _Encoding.GetBytes(value) select Rot13Byte(b)).ToArray();
               Out_TransformedLine = new string(_Encoding.GetChars(bytes));
            } else
               Out_TransformedLine = null; 
         }
      }

      string Out_TransformedLine {
         get { return _TransformedLine; }
         private set {
            Notify("Out_TransformedLine", value, ref  _TransformedLine);
         }
      }

Man braucht also 2 Properties, ein BackingField, und die Notify-Infrastruktur.
Und dann hat man immer noch die Häßlichkeit, dass NotifyPropertyChanged auch dann ausgelöst werden muß, wenn zwei identische lines hintereinander einlaufen, wenn sich die Read-Prop also gar nicht geändert hat.

Jo, an dem Punkt habichs an den Nagel, weil EBC feuert eben das Event, wenns dran ist, während Databinding erst ein BackingField füttern muß, und dann ein Event feuern. Und der Event-Parameter ist ein String, den man entweder einfach (und unsicher) hardcodet, oder mit ziemlichem Aufwand generiert [Artikel] INotifyPropertyChanged implementieren , auch sehr zu Lasten der Performance (was bei Daten mw. nicht so gravierend sein mag).

@ ralfw: 👍

Der frühe Apfel fängt den Wurm.