Laden...

EBC erweitern, um auf eine Anfrage einen direkten Rückgabewert zu bekommen?

Erstellt von ErfinderDesRades vor 13 Jahren Letzter Beitrag vor 13 Jahren 18.849 Views
ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 13 Jahren
EBC erweitern, um auf eine Anfrage einen direkten Rückgabewert zu bekommen?

Hallo!

Ich beschäftige mich grade mit dem Architektur-Konzept EBC, wassich durch diesen Blog von Ralf Westphal näher kennengelernt habe.

ZB. bastel ich an meinem Filebrowser herum, und schon das Konzipieren mit "Platinen" zeigt mir Implementations-Möglichkeiten von Wünschen, die ich bisher mir verkniffen hab, weil sie zu einem fürchterlichen Code-Dschungel geführt hätten.

Hier mal die Platine
(äh, meine Frage/Vorschlag kommt erst im nächsten Post)

Der frühe Apfel fängt den Wurm.

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

Dargestellt ist die Kommunikation zw. den 3 Komponenten, aus denen sich die Komponente "uclFileBrowser" zusammensetzt.
Die Pfeile kennzeichnen "Nachrichten-Drähte":
Die Komponente an der Pfeil-Wurzel raist ein Event, welches von der Komponente an der Spitze verarbeitet wird.
Die Pfeilwurzel nennt man Out-Pin, die Spitze ist der In-Pin der zielKomponente, weil das Event Daten von der Wurzel zur Spitze transportiert.

Mein EBC-Erweiterungs-Ansatz besteht in den Pfeilen "get current item" die am selben in-Pin des FileWebBrowsers ( = WebBrowser-Control-Erbe) enden. (Ich besprech mal nur den Pfeil vom NavigationPanel.)
Nämlich wenn der User im NavigationPanel auf "add to Bookmarks" klickt, soll das NavigationPanel ein Event raisen, welches das aktuell im FileWebBrowser angewählte Item returnt.

Das ist ein bischen verrückt, ein Event etwas returnen zu lassen, aber es spiegelt den tatsächlichen Vorgang wieder: Das Ereignis besteht nicht darin, dass das NavigationPanel Daten an den FileWebBrowser sendet, sondern dass es was von ihm wissen will.

Normales EBC täte anbieten, den Pfeil andersrum zu machen, und immer, wenn im FileWebbrowser das selektierte Item gewechselt wird, eine Notifikation ans NavigationPanel zu senden.
Das ist aber nicht erwünscht - ich will das Item nur dann abfragen, wenn's zu den Bookmarks zu adden ist.

technisch ist ein returnendes Event kein Problem:


      public event Func<string> Out_GetCurrentItem;

der In-Pin auch nicht:


      public string In_GetCurrentItem(){return "CurrentItemPath";}

und auch nicht die Verknüpfung

//...
this.navigationPanel1.Out_GetCurrentItem += this.fileWebBrowser1.In_GetCurrentItem;

Jetzt steht innerhalb von NavigationPanel jederzeit das FileWebBrowser.CurrentItem zur Verfügung:


this.bookMarks.Add(this.Out_GetCurrentItem());

bischen verrückt ist das, weil der Pfeil zwar richtigerweise den Nachrichtenfluß wiedergibt, der Datenfluß aber grad annersrum ist. Daher auch die bischen paradoxe Bezeichnungen, wie "OutGetPin".

Jedenfalls kommt man scheints an ein Design-Problem, was letztlich in einer Unzulänglichkeit(?) der Sprache c# begründet ist: Ereignisse sind immer Multicast-Delegaten, und daher können sie von mehreren Handlern abonniert werden. Das hat bei einem returnenden Event aber gar keinen Sinn - bei mehreren Handlern mit return-value verfallen alle return-values bis auf den letzten. Man bräuchte also Unicast-Events, die nur von einem Handler abonniert werden können.

Der frühe Apfel fängt den Wurm.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ErfinderDesRades,

Ralf verwendet mittlerweile keine Events mehr, sondern normale Delegaten. Er baut die Verknüpfungen daher nicht mehr mit += sondern mit einer einfach Zuweisung.

Ansonsten halte ich es für sehr ungesund, was du da vorhast. In EBCs gibt es eine klare Trennung zwischen Input- und Output-Pins. Input-Pins sind Methoden ohne Rückgabewert und mit einem Parameter, an den die eigentlichen Parameter übergeben werden, Output-Pins sind Delegaten ohne Rückgabewert und mit einem Parameter, an den der eigentliche Rückgabewert übergeben wird. Diese Trennung aufzugeben, stellt das ganze Konzept in Frage. Eingabe und Ausgabe sind in EBCs symmetrisch. Diese Symmetrie würdest du zerstören. Ich verstehe auch ehrlich gesagt nicht, was dich hindert, einfach eine Verbindung in die Rückrichtung zu machen.

herbivore

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

die trennung von in- und out- pins sehe ich nicht gefährdet: Out sind events (oder delegaten), In sind Methoden.
Und wenn auch, wozu darauf beharren? wichtig ist doch, dass die komponenten voneinander unabhängig sind, und daran täte sich ja nichts ändern. Von daher sehe ich kein Konzept in Frage gestellt.

Es ist allerdings eine Möglichkeit (auf die ich nicht gekommen bin), eine Wert-Abfrage durch 2 Nachrichten abzubilden. Ich finds nur wenig intuitiv, und viel Overhead.

(übrigens: auch delegaten kann man mit += abonnieren.)

Der frühe Apfel fängt den Wurm.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ErfinderDesRades,

dass das eine eine Methode und das andere ein Delegat ist, ist ein technisches Detail. Das hat nicht viel mit dem Konzept zu tun. Die Trennung von Eingabe und Ausgabe ist aber elementar. Daher ist das Konzept nicht nur gefährdet, wenn so umsetzt, wie du es anfänglich vorhattest, sondern es ist über den Haufen geworfen.

Wenn du EBCs benutzen willst, solltest du also den separaten Rückkanal wirklich einbauen.

Ich sehe auch den Overhead, den EBCs verursachen. Aber wenn man EBCs benutzen will, dann sollte man das wissen und auch in Kauf nehmen.

Oder man macht nur das Design per EBC, aber die Implementierung per IBC (dann hat man gar keine Delegaten mehr und Methoden haben ganz normale Parameter und Rückgabewerte). Letztere Idee gefällt mir bisher am besten. Das EBC Design zeigt sich dann in folgenden Punkten:

  • Komponenten, die Platinen entsprechen, kennen und enthalten nur noch solche Komponenten, die im Modell ihre Bauteile sind.
  • Komponenten, die Platinen entsprechen, haben Abhängigkeiten, machen aber nur einfaches, enthalten also nur den "Verdrahtungscode".
  • Komponenten, die Basisbausteinen entsprechen, haben keine Abhängigkeiten, dürfen dafür aber kompliziertes erledigen und implementieren die Domänenlogik.

Das man += auch auf Delegaten anwenden kann, ist mir bewusst. Ich habe ja auch nur gesagt, dass Ralf jetzt = statt += benutzt.

herbivore

U
208 Beiträge seit 2008
vor 13 Jahren

Zum Thema Overhead sei vielleicht noch gesagt, dass der gesamte Verdrahtungscode (und z.B. auch Grundgerüste der zu implementiertenden Komponenten) später einmal per Designer generiert werden soll. Insofern würde ich Optimierungen bzgl. des Overheads nicht unbedingt anstreben. Klar ist es derzeit noch nervig, aber seit sich das Thema EBC hier im myCSharp-Forum rumspricht, sehe ich rosige Zeiten auf das EBC-Konzept zukommen. 😉

Gelöschter Account
vor 13 Jahren

wenn es eines auf der welt gibt, das bei jedem großem projekt immer versagt.... dann ist das ein designer. ncihts für ungut aber designer haben keinen projekt-relevanten nutzen. auch ein ebc designer wird es nciht haben (meiner meinung nach.... aber das ist ein anderes thema)

was die reduktion des overheads angeht, so möchte ich dich dennoch anspornen nach alternativen zu suchen. allerdings sollten sie das modell nicht allzusehr vergewaltigen 😉

U
208 Beiträge seit 2008
vor 13 Jahren

Kann ich mir nicht vorstellen. Ich bin ziemlich überzeugt davon, dass es für EBCs über kurz oder lang einen vernünftigen Designer und Codegenerator geben wird, der dann gerade in großen Projekten sehr wichtig sein wird. Denn wo Dinge automatisiert werden, sinkt die Gefahr, Fehler einzubauen die sich schonmal gerne einschleichen können. Wir sind nunmal alle Menschen und machen Fehler, das ist ja auch ok, lässt sich nunmal nicht ändern.
Und gerade das Erzeugen der Code-Grundgerüste für die Komponenten ist ein Prozess, der eine Menge ausmacht. Ich meine, wenn man sich an die best practices der komponentenorientierten Entwicklung hält und jede Komponente in eine eigene Solution packt, wäre ich froh, wenn ich diese nicht von Hand im VS erstellen müsste.

(Und übrigens sind meine Erfahrungen gerade mit dem WinForms-Designer bisher sehr gut. Wenn ich da an die Java-Entwicklung unter Eclipse in der Berufsschule zurückdenke bei der wir keinen Designer hatten... Neee, das muss echt nicht sein. 😉 )

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ihr beiden,

ich möchte mal rechtzeitig prophylaktisch anmerken, dass das hier bitte keine Grundsatzdiskussion über den Sinn und Unsinn von Designern im Allgemeinen und auch nicht von EBC-Designern im Speziellen werden sollte.

herbivore

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

... aber die Implementierung per IBC (dann hat man gar keine Delegaten mehr und Methoden haben ganz normale Parameter und Rückgabewerte).

Also ich hab nicht viel zu IBC (interface based components) gefunden, ich stell mir das so vor, dass man erst die Interfaces hinschreibt, und dann implementationen dazu bastelt. Aber auch da fände ich die Umsetzung der Anforderung aufwändig:
Damit das NavigationPanel das currentItem abfragen kann, wenn der User im NavigationPanel auf "add to bookmarks" klickt, müsste bei IBC eine FileWebbrowser-Instanz - mw. mittels eines IFileWebbrowser-Interfaces aufs nötigste reduziert - ins NavigationPanel hineingegeben worden sein - oder würde man das anners machen?

Obige Platine in IBC umgesetzt hieße also:
Menu hält eine NavigationPanel-Instanz, NavigationPanel hält eine FileWebbrowser-Instanz, und FileWebbrowser hält eine NavigationPanel-Instanz und eine Menu-Instanz - kurz gesagt: jeder kennt (fast) jeden. Und FileWebbrowser kennte auch die SetHistoryMaxCount-Methode, die ihn gar nix angeht, die aber in INavigationPanel drin sein muß, damit Menu das nutzen kann. Oder kriegt FileWebbrowser ein anderes INavigationPanel serviert als es Menu bekommt?
Oder geht IBC anders (und wennjawie)?

Dagegen EBC, egal ob in meiner Version, in ralfs, oder auch im Sinne von Ted Faisen braucht streng genommen nicht mal interfaces, um Komponenten, die einander nicht enthalten, vollständig zu entkoppeln.
Wo "normalerweise" auf eine andere Komponente zugegriffen werden müsste (mittels Setter- oder Getter- (methode oder property mal egal)) steht bei EBC ein Event, und das muß eiglich nix kennen, nichtmal ein Interface.
Und "mein EBC" deckt nun sowohl Setter als auch Getter ab, wo ralfs "nur" Setter drauf hat, und für Getter einen Workaround braucht, mit hin- und rück- Setter (was sowohl im Diagram als auch im Interface, als auch im Code ziemlich umständlich daherkommt - Workaround eben).

Ich verstehe EBC eben als noch ziemlich neu, und im Fluß, und auch Ralf scheint keine große Scheu vor konzeptionellen Änderungen zu haben.

Der frühe Apfel fängt den Wurm.

3.728 Beiträge seit 2005
vor 13 Jahren
EBC und IBC

Hallo Zusammen,

hier werden schon ernsthafte Anstrengungen für einen EBC-Designer unternommen: Diagramme für EBCs (Event Based Components) zeichnen / eigenen EBC-Designer realisieren

Den Begriff IBC finde ich sehr schwammig, da er in den meisten EBC-Diskussionen für alles benutzt wird, was nicht EBC ist. Insofern ist IBC nicht wirklich genau definiert. Alles was "ganz normal" andere Methoden direkt aufruft.

U
208 Beiträge seit 2008
vor 13 Jahren

Hallo Rainbird,

Den Begriff IBC finde ich sehr schwammig, da er in den meisten EBC-Diskussionen für alles benutzt wird, was nicht EBC ist. Insofern ist IBC nicht wirklich genau definiert. Alles was "ganz normal" andere Methoden direkt aufruft.

also bei dem Begriff IBC (steht übrigens für Injection Based Component und nicht für Interface Based Component, wenn auch Interfaces bei IBCs, so wie Ralf sie formuliert, eine tragende Rolle spielen) habe ich zumindest eine ziemlich konkrete Vorstellung. Der unterschied von IBCs zu EBCs ist die Art, wie die Komponenten miteinander kommunizieren/in Abhängigkeit zueinander stehen. Bei IBCs sind die Abhängigkeiten fest über Verweise geregelt. Wenn eine übergeordnete Komponente mehrere Komponenten zusammenfasst, bzw. diese benutzt/von ihnen abhängig ist, äußert sich das so, dass für jede benötigte Komponente ein Konstruktorparameter vorgesehen ist. In der Regel werden diese Komponente über ein DI-Framework erzeugt, daher der Name IBC. Bei EBCs gibt es diese harten Abhängigkeiten eben nicht mehr. Wobei Ralf ja in seinem Blog schon viel zum Thema Komponentenorientierung allgemein geschrieben hat, ohne diese explizit IBCs zu nennen. Der Begriff entstand, wenn ich das richtig mitverfolgt habe, erst, nachdem die EBCs ins Spiel kamen, als Begriffsabgrenzung eben. Hoffe das klärt ein paar Sachen.

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

Was wäre eigentlich das Problem daran, wenn es Out-Pins gäbe, die statt Daten zu senden, welche returnen? Und entsprechende In-Pins, die statt Daten zu verarbeiten, welche eruieren?
Getter-Pins (In- und Out-) hätten halt keinen Parameter, stattdessen einen Rückgabewert.
Ich sehe In und Out auch weiterhin als klar getrennt. Der Unterschied zw. Delegat und Methode scheint mir mehr als eine technische Detailfrage. ZB. kann man Delegaten von außen zuweisen und adden, Methoden nicht.
Wie man das dann Generier- und Tool-fähig kriegt, ist eine andere Geschichte.
Vlt. verwendet man für Get-Nachrichten ein anderes Pfeil-Symbol, also statt A------->B mw A-------<B, und dann erstellt das tool entsprechend anderen code, das gute ist ja, es gibt noch keines 😉

Mein Grundgedanke ist gewissermaßen, zwischen Nachrichtenfluß und Datenfluß zu unterscheiden, und bei Getter-Nachrichten ist der Datenfluß halt gegenläufig.
Eine Nachricht, die keine Daten transportiert, wäre mir sehr zuwider. Und genau das hätte man, wenn man einen Draht ziehen muß, nur um mitzuteilen, dass man Daten gesendet bekommen möchte.

Ralfs Lösung des Return-Value-Problems findet sich übrigens hier, ungefähr in der mitte, und gefällt mir nicht sonderlich: Er geht von der Vorgabe "Out-Pin ist ein Event mit 1 Parameter" ab und versendet 2 Parameter, und der 2. ist ein Action<TReturn> - delegat, den der Empfänger aufruft, wodurch im Sender wiederum was bewirkt wird:


interface IParser {
   void ProcessParseRequest(string source, Action<ASTNode> processResult);
}

Einen Rückgabewert "erschleicht" er sich dann durch folgenden Trick:


class CompilerCoordinator : ICompilerCoordinator {
    public void ProcessCompilationRequest(CompilationRequest request)    {
        ASTNode program;
        this.OnParseRequest(request.Source, r => program = r);
        …
    }

Das nenne ich mal Vermischung von In- und Out-Pin. Ich hab Schwierigkeiten, mir davon eine Darstellung im Diagramm vorzustellen - formal ist es ja eine Nachricht vom CompilerCoordinator an den Parser, also A---->B. Da aber B auch Daten zurücksendet, quasi durch eine Röhre in der Röhre - sollen wir den Pfeil so machen: A---<-->B ?
Ich hätte die Anforderung "CompilerCoordinator sendet request-Daten und erhält im Austausch Result-Daten" tatsächlich über 2 Pfeile gelöst, denn die request-Nachricht ist ja nicht leer. (Und wenn ein Parser was geparst hat, dann darf er auch mal ein Event senden, findich - ist ja auch praktisch zu tracen.)

Der frühe Apfel fängt den Wurm.

742 Beiträge seit 2005
vor 13 Jahren

Mit Rückgabewerten skaliert die Anwendung dann leider nicht mehr so gut. Eine Umstellung auf Threads oder sogar Remoting fällt dann deutlich schwerer.

Deshalb würde ich von solchen Adaptionen abraten und beim reinen Messaging bleiben.

3.728 Beiträge seit 2005
vor 13 Jahren
Mythen

Mit Rückgabewerten skaliert die Anwendung dann leider nicht mehr so gut. Eine Umstellung auf Threads oder sogar Remoting fällt dann deutlich schwerer.

Als jemand, der sich igerade intensiv mit dem Verteilungsaspekt von EBCs befasst, muss ich da widersprechen. Die Anwendung ist nicht mehr oder weniger skalierbar, egal ob Request-Response-Aufrufe (also Methoden mit Rückgabewerten) gemacht werden, oder nicht. Für die Skalierbarkeit ist entscheidend, dass die serverseitigen Komponenten statuslos sind und eine möglichst kurze Lebenszeit haben (oder über Objektpool verwaltet werden). Der großen Serverfarm ist es egal, wie die Methoden aussehen. Da kommt es auf den effektiven Umgang mit den Serverressourcen an und auch darauf, dass die Kommunikation nicht zu "chatty" ist, da dies hohen Netzwerk-Overhead zur Folge hätte.

Was die Verteilung an sich angeht, ist es sogar einfacher, mit Rückgabewerten zu arbeiten, da die gängigen Kommunikations-Frameworks WCF und Remoting beide primär auf RPC fokussiert sind.

Wenn ich Request-Response haben will, dann muss das synchron sein. Der Anfrager, wartet ja auf seine Antwort. Da bringt Multithreading nix. Es sei denn eine Berechnung wird aus Performanc-Gründen in mehrere Threads aufgeteilt. Dann wartet die Anfrager-Komponente eben solange, bis alles verarbeitet ist. Deshalb sehe ich auch da bei Rückgabewerten kein Problem.

Die Verwendung von Func<Q,R> gegenüber Action<T> hat einen ganz anderen Nachteil. Es kann nur die Komponente die Antwortnachricht empfangen, die auch die Anfrage gestellt hat. Das ist eine große Einschränkung.

Wenn ich das aber weiss und mir diese Einschränkung nichts ausmacht, dann ist es okay.

Es kommt immer darauf an, was man erreichen will. Pauschalaussagen sind meistens falsch!

71 Beiträge seit 2010
vor 13 Jahren

Wenn ich Request-Response haben will, dann muss das synchron sein. Der Anfrager, wartet ja auf seine Antwort. Da bringt Multithreading nix

Das sehe ich anders. Req/Response bedeutet nur, dass ein Sender vom Empfänger eine Antwort haben will. Das hat erstmal nichts mit sync/async zu tun.

Wir sind gewohnt, das als Funktion zu notieren und sync zu denken.

Aber async gehts auch. Das machst du auch täglich im Büro. Du schreibst per Email eine Frage an einen Kollegen und erwartest natürlich eine Antwort. Für die bleibst du aber nicht vor dem Rechner kleben, bis er geantwortet hat. Das ist ja der Trick bei Email gegenüber Telefon. Email ist ein async Kommunikationsmedium.

Genauso bei EBC (oder EDA). Natürlich kannst du einen Request losschicken und als Absender erwarten, dass irgendwer eine Antwort liefert. Du musst die nur ggf. dem Request zuordnen (Korrelation).

Damit diese Korrelation einfach ist, gibt es in meinem Beispiel im Request eine "Adresse" für die Antwort. Dadurch werden Continuations möglich: Ich kann am Ort des Versands sagen, wohin die Antwort kommen soll - und dieser Ort kann gleich dort ad hoc in Form einer Lambda Funktion "aufgespannt" werden.

Aber nochmal allgemeiner:

Ich bin inzw der Meinung, dass es ok ist, EBC-Pins als Func<TRequest, TResponse> auszulegen. Man muss nur wissen, dass diese Pins nicht an Thread/Remoting-Grenzen benutzt werden sollten. Wenn man das im Blick hat, find ich es der Bequemlichkeit halber ok. Immer nur Action<T> wäre vielleicht wirklich ein bisschen zu päpstlich 😉

Also: Für sync EBC-Architekturen Action, Action<T> und Func<TRequest, TResponse>, Func<TResponse> als Delegatentypen.

Für async EBC-Architekturen nur Action und Action<T>.

Warum das? Weil nur Action die grundlegende Asynchronizität vernünftig ausdrückt.

71 Beiträge seit 2010
vor 13 Jahren

Getter-Pins (In- und Out-) hätten halt keinen Parameter, stattdessen einen Rückgabewert

Darin kann ich leider grad keinen Vorteil sehen. Nach Occams Rasiermesser würde ich mal sagen, dass die Zahl der Konzepte in EBC nicht dafür erhöht werden sollte. Ich sehe keine Not dafür.

3.728 Beiträge seit 2005
vor 13 Jahren
Splitter

Hallo Ralf,

Func<Q,R> hat den Vorteil, dass ich bei Komponenten, die viele Operationen anbieten, nur die Hälfte der Pins in der Schnittstelle habe.

Wenn ich dann doch mal die Antwortnachricht woanders hinschicken muss, dann setze ich einen Splitter dazwischen:


public class RequestResponseSplitter
{
    public Action<T> Out_Response;

    public Func<Q,R> Out_RequestResponseCall;

    public object In_SplitCall(object message)
    {
        object response=Out_RequestResponseCall(message);

        Out_Response(response);
    }
}

Und schon bin ich wieder im Geschäft!

742 Beiträge seit 2005
vor 13 Jahren

@Rainbird: Zum Thema Skalierung.

Im Normalfall hast du wahrscheinlich absolut Recht. Ich habe über das Szenario nachgedacht, asynchron und verteilt zu arbeiten. Wieso soll ich dann Threads blockieren, die auf Rückantwort warten?

Vielleicht habe ich auch gerade einen Denkfehler, aber ich denke bei vielen, gleichzeitigen Operationen kann das nicht der beste Weg sein.

3.728 Beiträge seit 2005
vor 13 Jahren
Kommunikation

Warum jemand auf die Rückantwort warten soll, hat folgenden Grund:
Wenn ich möchte, dass Komponente A eine bestimmte Arbeit erledigt, bei der ein Ergebnis produziert wird, dann kann ich leider nur Däumchen drehen, bis das Ergebnis da ist. Es sei denn ich habe noch andere Sachen zu tun, die ich in der Zwischenzeit machen kann. Wenn ich aber z.B. ein neu erfasstes Angebot einbuche, dann warte ich - als Mensch - die 60 ms, bis die Rückmeldung da ist, dass alles geklappt hat (oder auch nicht).

Wenn ich dagegen eine Dublettenprüfung meiner 200.000 Kunden-Stammsätze anstoße, dann möchte ich nicht solange blockiert sein, bis das fertig ist (es könnte viele Minuten dauern). Dann lasse ich mit async. per Nachricht informieren, wenn es fertig ist.

Das isnd zwei ganz vierschiedene Paar Schuhe.

Typische Einsatzgebiete für asynchrone Kommunikation sind auch Fortschrittsbalken. Ich möchte z.B. wissen, wie weit mein Upload zum Server ist.

Bei verteilten Komponenten sieht es so aus, dass für jede Client-Anfrage ein eigener Arbeits-Thread erzeugt wird. Sonst könnte der Applikationsserver ja nur einen Client zu selben Zeit bedienen. Mit der Beschaffenheit der Methoden hat das aber wenig zu tun.

742 Beiträge seit 2005
vor 13 Jahren

@Rainbird: Ich denke du hast Recht. Ich habe aber irgendwie ein ungutes Gefühl dabei, wahrscheinlich liegt das aber daran, dass ich mich noch nicht daran gewöhnt habe, dass EBCs viel Feingranularer sind als ich bisher z.B. asynchrone Komponenten entwickelt habe oder verteilte Systeme.

Es ist in der Tat auch verdammt unbequem, komplexer und wahrscheinlich sogar performancetechnisch aufwändiger für jede simple Addition die Rückantwort über einen seperaten Kanal zu empfangen.

@Ralf: Was macht eigentlich der Garbage Collector bei deiner Lösung des Rückgabewertes über Lambdas? Was würde passieren, wenn z.B. aus technischen Gründen bei einer langlebigen Komponente vermehrt keine Antworten gesendet werden?

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ErfinderDesRades,

Obige Platine in IBC umgesetzt hieße also:
Menu hält eine NavigationPanel-Instanz, NavigationPanel hält eine FileWebbrowser-Instanz, und FileWebbrowser hält eine NavigationPanel-Instanz und eine Menu-Instanz - kurz gesagt: jeder kennt (fast) jeden.

nein, nein, nein, das hast du vollkommen falsch verstanden. Ich sprach davon ein EBC-Design in IBC umzusetzen. Da gäbe es dann genau dieselben Abhängigkeiten wie bei EBC, nicht mehr und nicht weniger. Insbesondere gäbe es also weiterhin eine Platine. Nur dass die Komponenten eben keine Input- und Output-Pins haben, sondern ganz normale Methoden und die Platine, eben nicht die Input- und Output-Pins verbindet, sondern die Methoden ganz normal aufruft. Den Unterschied siehst du in folgendem Beispiel:
EBC: Validierung und Exception Handling

herbivore

71 Beiträge seit 2010
vor 13 Jahren

Was macht eigentlich der Garbage Collector bei deiner Lösung des Rückgabewertes über Lambdas? Was würde passieren, wenn z.B. aus technischen Gründen bei einer langlebigen Komponente vermehrt keine Antworten gesendet werden?

Ich vermute, du unterscheidest nicht zw sync und async.

In sync Szenarien gibt es keine "langlebigen" Komponenten in deinem Sinne. Komponenten mögen zwar Singletons sein und während der gesamten Laufzeit existieren. Aber die Nachrichten fließen immer durch sie durch - und am Ende, wenn sie verarbeitet sind, wird der gesamte Callstack abgebaut. Damit werden auch temporäre Lambda Ausdrücke entsorgt, die mit Requests in Komponenten hineingekommen sind.

Bei async Architekturen ist da ein leichter Unterschied. Da können ja viele Requests in eine Komponente reinlaufen. Insofern können sich in den vielen gleichzeitigen Callstacks auch Lambdas ansammeln. Doch auch die werden wieder freigegeben, wenn ein Callstack fertig ist, d.h. der Thread wieder einer anderen Aufgabe zugeordnet werden kann.

Ich sehe also kein Problem, solange du nicht dynamische Pins in globalen Vars hälst. Aber warum solltest du das?

Das sehe ich nur, wenn ich die dynamischen Pins eben nicht weiterreichen will. Das kann sein, wenn nur der Request weitergereicht wird und später ein Response mit dem Response-Pin des Request manuell korreliert werden soll. Dann muss sich jmd die ganzen Response-Pins merken. In Remoting-Szenarien ist das der Fall, wenn es pro Servicekomponente nur einen Stub gibt.

Dann ist da eine GC nötig. Ich würde die Response-Pins nur über eine gewisse Zeit speichern (lease time) und entsorgen, wenn die abgelaufen (und nicht verlängert) ist. Das ist kein Hexenwerk.

71 Beiträge seit 2010
vor 13 Jahren

Warum jemand auf die Rückantwort warten soll, hat folgenden Grund:
Wenn ich möchte, dass Komponente A eine bestimmte Arbeit erledigt, bei der ein Ergebnis produziert wird, dann kann ich leider nur Däumchen drehen, bis das Ergebnis da ist. Es sei denn ich habe noch andere Sachen zu tun, die ich in der Zwischenzeit machen kann. Wenn ich aber z.B. ein neu erfasstes Angebot einbuche, dann warte ich - als Mensch - die 60 ms, bis die Rückmeldung da ist, dass alles geklappt hat (oder auch nicht).

Das hab ich mir gedacht, dass du so denkst 😉 Das ist die gängige Denke - aber ich halte sie für veraltet und beengend.

Benutzerschnittstellen sollten konsequent async ausgelegt werden, soweit es irgend geht. Anders kommst du nicht dahin, Multicores wirklich dahinter auszunutzen.

Ich male dir ein anderes Szenario:

Wenn ich ein erfasstes Angebot einbuche, dann will ich als Anwender eben nicht warten. Nicht mal 1 Sek. Ich will "Buchen" klicken und weiter gehts mit irgendwas anderem.

Nun sagst du natürlich, da kann doch aber ein Fehler auftreten.

Und ich antworte: Klar. Aber ein Fehler ist hoffentlich sehr selten. (Ein technischer sowieso, aber auch ein inhaltlicher. Ansonsten sollte man wohl eher mal in der Fachabteilung überlegen, was das Wurzelproblem ist.)

Wenn ein Fehler selten ist, dann will ich aber einen Verarbeitungsprozess nicht darauf auslegen.

Ich programmiere heute dieses Szenario immer so: Erfassen, abschicken, Verarbeitung im Hintergrund. Fertig. Falls ein Fehler auftritt, wird der Anwender informiert (natürlich nicht mit einer Messagebox). Er kann dann, wenn und wann es ihm passt, die erfassten Daten wieder aufrufen und den Fehler beheben.

Niemand arbeitet im realen Leben mit Menschen synchron. Es ist eine unsägliche Angewohnheit der Entwickler, ihre Software dem zuwiderlaufen zu lassen. Diese Angewohnheit stamm aus einer "Maschinendenke" und aus Zeiten begrenzter Ressourcen. Heute jedoch ist das anachronistisch.

742 Beiträge seit 2005
vor 13 Jahren

Ich vermute, du unterscheidest nicht zw sync und async.

Doch, schon. Ich hätte wohl explizit erwähnen müssen, dass es um async Komponenten geht. Ich gehe gedanklich irgendwie immer von Async aus, weil für mich die EBCs näher an Messaging oder dem Application Space ist als an synchronen Komponeten sind.

Dann ist da eine GC nötig. Ich würde die Response-Pins nur über eine gewisse Zeit speichern (lease time) und entsorgen, wenn die abgelaufen (und nicht verlängert) ist. Das ist kein Hexenwerk.

Kein Hexenwerk, aber es muss halt getan werden. Für die Theorie und Beispiele ist das natürlich unwichtig, aber ich war mir gerade nicht sicher. Da ich Korrelationen bisher noch nie über Lambdas geregelt, sondern die Request Nachricht explizit in einer Liste vorgehalten habe, habe ich mir nie Gedanken drüber gemacht.

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

Getter-Pins (In- und Out-) hätten halt keinen Parameter, stattdessen einen Rückgabewert

Darin kann ich leider grad keinen Vorteil sehen. Nach Occams Rasiermesser würde ich mal sagen, dass die Zahl der Konzepte in EBC nicht dafür erhöht werden sollte. Ich sehe keine Not dafür.

Oh, und ich hatte gedacht, indem Parameter und Rückgabewerte in derselben Nachricht zugelassen würden, dadurch hätte man die Zahl der Konzepte erhöht.
Weil ein Konzept hieß doch: "nur ein Parameter pro Nachricht". Das habich bischen weitergedacht, und führt mich logisch zu Func<T> als Ergänzung zur Action<T>, und keine T2, T3 dazu.

Mein Konzept-Entwurf ließe also folgende Varianten zu:


public Action<TSetter> SetAction;
public Func<TGetter> GetFunc;

public void SetMethod(TSetter value);
public TGetter GetMethod();

Das finde ich ein sehr übersichtliches Konzept, und sicher leichter generierbar als wenn nun auch noch


public Func<TRequest, TResult> RequestFunc;
public Func<TRequest1, TRequest2, TResult> RequestFunc;
//...

public TResult RequestMethod(TRequest data);
public TResult RequestMethod(TRequest1 data1, TRequest2 data2);
//...

hinzukämen.

Durch das Konzept von Setter- und Getter-Drähten ist quasi das herkömmliche Property-Konzept vollständig in EBC abgebildet (türlich kopplungs-frei).

Nicht abgebildet ist die endlose Varianten-Vielfalt von Methoden mit variabler Anzahl von Parametern, zuzüglich ggfs. des Return-Wertes.

Aber warum eigentlich nicht? Wäre zwar bestimmt einiges aufwändiger zu toolen, aber nicht unmöglich.

Der frühe Apfel fängt den Wurm.

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

Oder leuchtet dir die Notwendigkeit eines Getter-Drahtes nicht ein? Die zeigt sich finde ich klar, im Eingangs-Beispiel, wo das NavigationPanel das CurrentItem vom FileWebbrowser abfragen muß, um es seinen Bookmarks hinzuzufügen. Sowas tritt all nas lang auf, auch in deinem eigenen Beispiel, mit dem Rot13-Transformater.
Es ist m.E. nämlich unglücklich designed, dass die Configuration ihren DefaultPath im Moment des Konfigurierens an TextFile-Reader und -Writer sendet, sodaß der Wert an 2 Stellen zwischengelagert werden muß, bisser jeweils gebraucht wird.
Logischer wäre doch, wenn die TextFile-Dinger den DefaultPath von der Config abfragen können, in dem Moment, wo sie ihn brauchen. Also ein Getter-Draht muß her, in Analogie zu Property.Get.

Der frühe Apfel fängt den Wurm.

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

nein, nein, nein, das hast du vollkommen falsch verstanden... Den Unterschied siehst du in folgendem Beispiel:

>

Ah, glaub, es dämmert, thx! Irregeleitet hatte mich wohl dein Statement, in IBC käme man ohne Events aus. Weil beim FileBrowser bräuchte man jede Menge Events, weil die Komponenten aktiv sind (anders als beim Compiler-Rechenwerk).
Tatsächlich würde der EBC-Konstruktor dem IBC-konstruktor stark ähneln, weil alle Events der Komponenten ebenso verdrahtet werden müssten, nur würden sie nicht direkt untereinander verdrahtet, sondern auf Handler, die auf der Platine sitzen.
Son Platinen-Handler würde mw. ein GotoItem-Command-Event des NaviPanels an die SetCurrentItem-Methode des FileWebbrowsers durchreichen. Da hätte IBC also mehr Overhead als EBC.
Interessant übrigens, dass auch IBC Getter-Events benötigen täte, um die NaviPanel-Anforderung "hole mir das Current selected item" adäquat umzusetzen. Das würde man klassischerweise wohl mit modifizierbaren EventArgs umsetzen, rel. unschön, weil so ein Event es nicht sonderlich deutlich nahelegt, dass der Handler im EventArg was eintragen muß.

Ich hab übrigens auf der FileBrowser-Platine weitere Bedarfe für Getter-Drähte festgestellt:

Der frühe Apfel fängt den Wurm.

71 Beiträge seit 2010
vor 13 Jahren

Oh, und ich hatte gedacht, indem Parameter und Rückgabewerte in derselben Nachricht zugelassen würden, dadurch hätte man die Zahl der Konzepte erhöht.

Nein, dadurch erhöht sich die Zahl der Konzepte nicht. Im Gegenteil: sie wird bewusst klein gehalten. Denn 1 Parameter bedeutet ja nicht, dass der nicht strukturiert sein darf. Mit dem 1 Parameter kannst du etwas ganz kompliziertes versenden. Kein Problem.

In dem 1 Parameter einen Endpunkt für die Antwort zu verpacken, ist ein Standardmuster in der async oder Event-orientierten Programmierung.

Ich sehe auch nicht, dass deine Getter etwas leichter machen. Sie verschleiern eher das, was offen liegen sollte: das ein Signal vom Client zum Service läuft, um einen Wert abzuholen, der dann zurück läuft.

Wie schon gesagt: für sync Szenarien find ich es ok, Func<,> als Delegatentyp zu nutzen. Aber man muss sich bewusst sein, dass das eben nur bei sync Szenarien geht. Async Kommunikation kennt keine Funktionen.

Zum Rot13-Beispiel und der Konfig: Es ist schlicht ein anderes Denken, wenn man EBC modelliert. Da ist öfter Push drin, wo du Pull (mit Gettern) denkst. Wenn also während der Config-Phase Werte in Komponenten gepusht werden, ist das normal für EBC - und nur in einer IBC/OOP Welt merkwürdig. (Da wird dann auch nichts doppelt gehalten. Und selbst wenn. Wo ist das Problem?)

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

Oh, und ich hatte gedacht, indem Parameter und Rückgabewerte in derselben Nachricht zugelassen würden, dadurch hätte man die Zahl der Konzepte erhöht.

Nein, dadurch erhöht sich die Zahl der Konzepte nicht. Im Gegenteil: sie wird bewusst klein gehalten. Denn 1 Parameter bedeutet ja nicht, dass der nicht strukturiert sein darf. Mit dem 1 Parameter kannst du etwas ganz kompliziertes versenden. Kein Problem.

Hmm - reden wir annander vorbei?
Du bist es doch, der die Anzahl der Parameter pro Nachricht erhöht, indem beim Request sowohl Daten als auch Rückgabewert/Continuation vorgesehen werden.
Ich dagegen bleibe dem Konzept "1 Parameter pro Nachricht" doch ganz getreulich treu 😉.
Dass die Parameter auch komplex sein dürfen, denkich auch so. Nur in meiner Denke gilt ein Rückgabewert auch als Nachrichten-Parameter, und deshalb fliegt in dem Fall das Methoden-Argument raus, nämlich um dem Konzept treu zu bleiben.

Das mit dem Verschleiern bestreite ich energisch. Wie kann ein Pull-Vorgang klarer offengelegt sein als durch Signaturen mit einem Rückgabewert und sonst nichts?

Klar ist ein ReturnValue bei Async nicht einsetzbar. Ich würde bei Async die Continuation auch durch einen Extra-Draht darstellen.
Ein Reinstopfen der Continuation (und ggfs. auch der Progress-Reports) in dieselbe Nachricht (sei es durch komplexes Argument oder durch Extra-Argument) - das finde ich verschleiernd. Denn die Continuation ist eine Nachricht in Gegenrichtung, und beim Reinstopfen wird das im Diagramm nicht sichtbar.
Auch die Code-Generation dürfte bei einem 1-wire-Request schwieriger sein, weil der Generator den Parameter viel genauer analysieren müsste als bei einem in 2 Drähten ausgeführtem Request. Tatsächlich sehe ich grad gar nicht, wie für eine mitgelieferte Continuation generiert werden kann, was beim Extra-Draht ja überhaupt kein Thema ist.
Aber dieses Thema "Bidirektionalität" müssenwa klar trennen vom Thema "Getter-Draht"

Zum EBC-Denken: Was ich sagen will, ist, dass die EBC-Denke unnötig beschränkt wird, dadurch, dass Pull strukturell nur mühsam unterstützt ist. Das mag zwar normal sein, geht aber besser.

Türlich wird beim Rot13-Sample der DefaultPath doppelt gehalten, sogar 3-fach: einmal inne Config (wos ja hingehört), einmal im TextWriter, und einmal im TextReader.
Wenn EBC unbeschwert Pull denken könnte, würden sich die Felder private readonly Configuration config; (im Sample steht sogar jeweils(!) eine eingeschachtelte Spezial-Klasse "Configuration" dahinter) sowohl im Reader als auch im Writer auf ganz natürliche Weise erübrigen.
Die Text-Reader/-Writer - Klassen wären schlanker, und Redundanz wäre eliminiert.

Hey, ich würde sogar so weit gehen, das in den Rang eines Antipattern zu erheben (jetzt mal unabhängig von EBC):
"Wenn Push gedacht wird, wo Pull hingehört, entstehen überflüssige BackingFields, in denen die gepushten Werte geparkt werden müssen, bis sie wirklich gebraucht werden."

Der frühe Apfel fängt den Wurm.

71 Beiträge seit 2010
vor 13 Jahren

Du bist es doch, der die Anzahl der Parameter pro Nachricht erhöht, indem beim Request sowohl Daten als auch Rückgabewert/Continuation vorgesehen werden.

Das ist 1 Parameter:

event Action<Query<string, Customer>> Out_GetCustomerById;

class Query<TPattern, TResponse>
{
    public TPattern Pattern;
    public Action<TResponse> ResponsePin;
}

Und das lässt sich durchaus auch in einem EBC-Diagramm darstellen. Und das ist sofort async-kompatibel. Und das ist ein anerkanntes Pattern.

Ein Pull ist kein Event mehr. Wie willst du ein Pull als im einen Interface einer EBC abbilden? Du kannst nur ein Func<TResponse> benutzen.

Mir schmeckt Func<TResponse> allerdings nur als Sonderfall eines Request/Response-Event und nicht als neues Konzept "Getter".

In jedem Fall gehören Properties für mich ganz und gar nicht in die EBC-Welt.

Und dass beim Konfig der Wert 3 Mal gehalten wird, ist aus meiner Sicht völlig unerheblich; allemal ist das kein Nachteil. Sowas passiert in IBC-Architekturen auch andauernd, indem z.B. Config-Strings über Ctors injiziert werden.

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

ok, hier ist die Response nicht als 2. Parameter in die Nachricht gestopft, sondern als Element eines komplexen Parameters.

Das sähe im Diagram wohl so aus:

Der frühe Apfel fängt den Wurm.

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

bei mir käme:

Der frühe Apfel fängt den Wurm.

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

... Im Diagramm klar erkennbar, dass eine Nachricht hingeht, eine andere zurück
und folgender (vereinfachter) Code würde generiert:


   public class Client {
      public Action<Pattern> Out_QueryPattern;
      public void In_QueryResponse(Response value) { }
   }
   public class Server {
      public Action<Response> Out_QueryResponse;
      public void In_QueryPattern(Pattern value) { }
   }

Bei dir ist im Diagramm die Rück-Nachricht so nicht ablesbar - würdest du ein bidirektionales Pfeil-Symbol verwenden?

Dein Code käme glaub so daher:


   public class Client {
      public Action<Query<Pattern, Response>> Out_Query;
   }
   public class Server {
      public void In_Query(Query<Pattern, Response> value) { }
   }

Wie würdest du jetzt den In_Response-Pin verdrahten? In deinem Blog ist der ja als anonyme Methode ausgeführt - aber ein Generator würde wohl einen privaten In-Pin erzeugen, oder?

Der frühe Apfel fängt den Wurm.

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

Ein Pull ist kein Event mehr. Wie willst du ein Pull als im einen Interface einer EBC abbilden? Du kannst nur ein Func<TResponse> benutzen.

geht problemlos


   public interface INavigation {
      event Func<string> GetCurrentItem;
   }
   public class Navigation : INavigation {
      public event Func<string> GetCurrentItem;
   }
   public interface IFileWebBrowser {
      string GetCurrentItem();
   }
   public class FileWebBrowser : IFileWebBrowser {
      public string GetCurrentItem() {
         throw new NotImplementedException();
      }

Mir schmeckt Func<TResponse> allerdings nur als Sonderfall eines Request/Response-Event und nicht als neues Konzept "Getter".

schade - ich geb mir sone Mühe, es dir schmackhaft zu machen 😉

In jedem Fall gehören Properties für mich ganz und gar nicht in die EBC-Welt.

zustimm. Properties koppeln.
Aber außerdem enthält das Konzept Property die beiden TeilKonzepte Push und Pull, und ich halte es für schädliche Selbst-Beschränkung, wenn EBC nur das Push-Konzept übernehmen würde.

Und dass beim Konfig der Wert 3 Mal gehalten wird, ist aus meiner Sicht völlig unerheblich; allemal ist das kein Nachteil.

ok, groß erheblich ist es in diesem Fall nicht, aber _völlig _unerheblich auch nicht, und die Nachteile habichdoch aufgezigt: Die Text-Klassen sind bis zu 50% Code zu fett, und Redundanzen sind vom Prinzip her böse.

Sowas passiert in IBC-Architekturen auch andauernd, indem z.B. Config-Strings über Ctors injiziert werden.

Wie gesagt, es mag üblich sein, ist aber _deswegen _nicht gut (kann natürlich sein, dasses aus anderen Gründen gut ist).
Vor allem geht es ja gar nicht um Config, sondern es zeigt sich bei deinem Sample nunmal zufällig am Config-Design ein Pull-Bedarf, wie er sich beim FileBrowser (wesentlich zwingender) bei GetCurrentItem zeigt (sowie bei GetViewMode und GetHistoryMaxCount).

Der frühe Apfel fängt den Wurm.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ErfinderDesRades,

Oder leuchtet dir die Notwendigkeit eines Getter-Drahtes nicht ein?

auf den ersten Blick ist das verlockend. Aber auf den zweiten Blick verbietet es sich bei EBCs auch gleich wieder. Du betrachtest die Sache aus dem Blickwinkel der einfachen Realisierung eines bestimmten "Benutzers" der Komponente. Aber es kann ja auch andere Benutzer geben. Der Vorteil von EBCs ist doch, dass man den Output dahin leiten kann, wohin man will. Im Allgemeinen wird der Rückgabewert der "Property" nicht zwangsläufig von demjenigen gebraucht, der die Abfrage der "Property" anstößt.

Nehmen wir mal an, die Komponente A will eine "Property" von Komponente B abfragen. Dann würde - durch die entsprechende Verdrahtung - der Request von A nach B und die Response von B nach A geschickt. Zwei Leitungen. Aber es kann ja auch Fälle geben, in denen nicht A das Ergebnis braucht, sondern C. Dann würde die Platine die zweite Leitung einfach von B nach C legen.

Dein Vorschlag läuft aber darauf hinaus das Interface von B so zu ändern, dass es nur eine Leitung gibt. Das Ergebnis würde dann zwangsläufig zu A zurückfließen, auch wenn C es braucht. Du schränkst also die Komponente B ein, änderst deren Interface, nur weil das für ein bestimmtes A gerade besser so ist. Das ist keine gute Grundlage.

Daher halte ich - wenn man denn schon EBCs macht - nichts von Getter-Drähten.

Tatsächlich würde der EBC-Konstruktor dem IBC-konstruktor stark ähneln, weil alle Events der Komponenten ebenso verdrahtet werden müssten, nur würden sie nicht direkt untereinander verdrahtet, sondern auf Handler, die auf der Platine sitzen.

Sicher, wenn es bei IBCs auch ein paar Events zu "verdrahten" gibt, dann würde die "Verdrahtung" im Konstruktor erfolgen. Aber deswegen braucht man nicht zwangsläufig EventHandler in der "Platine". Wenn die Signatur von Event und aufzurufender Methode passt, kann man die aufzurufende Methode natürlich direkt an das Event binden. Wenn die nicht passt, würde auch bei EBCs Overhead entstehen und zwar sogar noch mehr, weil man dann eine extra Komponente für die Umsetzung schreiben müsste. In IBCs würde ein einfacher EventHandler in der "Platine" reichen, um die Umsetzung zu realisieren. Der Overhead ist also bei IBCs immer geringer als bei EBCs.

Son Platinen-Handler würde mw. ein GotoItem-Command-Event des NaviPanels an die SetCurrentItem-Methode des FileWebbrowsers durchreichen. Da hätte IBC also mehr Overhead als EBC.

Wie gesagt, man braucht so einen EventHandler nur, wenn eine Umsetzung erforderlich ist. Wenn die erforderlich ist, dann ist der Overhead bei EBCs aber noch größer. Außerdem hat man bei EBCs immer den Overhead in Form der Doppelung der Anzahl der Member in Interface und Komponenten (durch die Aufteilung von Methoden mit Rückgabewert in Input- und Output-Pin) überall. IBCs schneiden in Sachen Overhead auf jeden Fall besser ab als EBCs.

Interessant übrigens, dass auch IBC Getter-Events benötigen täte, um die NaviPanel-Anforderung "hole mir das Current selected item" adäquat umzusetzen. Das würde man klassischerweise wohl mit modifizierbaren EventArgs umsetzen, rel. unschön, weil so ein Event es nicht sonderlich deutlich nahelegt, dass der Handler im EventArg was eintragen muß.

Nein, ein solche Konstruktion benötigt man nicht. Der Ablauf wäre ganz anders. Das MenuItem.Click würde auf der Haupt-Platine ankommen - entweder direkt oder weitergeleitet von deiner Menu-Komponente. Bis hier ging es nur um simpelste EventHandler mit EventArgs.Empty. Im folgenden braucht man keine Events mehr, denn die IBC-Hauptplatine nimmt die weitere "Verdrahtung" ja durch einfache Property- und Methodenaufrufe vor. Sie würde also die CurrentItem-Property des FileWebBrowsers abrufen und den Wert per Parameter an die SetBookmark-Methode übergeben. Also von komplizierten Events keine Spur.

herbivore

71 Beiträge seit 2010
vor 13 Jahren

Bei dir ist im Diagramm die Rück-Nachricht so nicht ablesbar - würdest du ein bidirektionales Pfeil-Symbol verwenden?

Ja, ein bidirektionaler Pfeil (allerdings asymmetrisch, so dass man erkennen kann, wer die Kommunikation initiiert).

Wie würdest du jetzt den In_Response-Pin verdrahten? In deinem Blog ist der ja als anonyme Methode ausgeführt - aber ein Generator würde wohl einen privaten In-Pin erzeugen, oder?

Ein Generator würde keinen Antwort-Pin erzeugen. Das ist ja der Trick. Nur so ist beim Initiator halbwegs eine Sequenz von Operationen darstellbar.

Hier etwas Code eines Initiators/Clients, der auf Typen und Erweiterungsmethoden basiert, die ich in meinem Blog schonmal vorgestellt habe:

Customer cust;

this.Out_GetCustomerById
  .Request("123")
  .Receive(c => cust = c);
...
if (this.Out_ValidateOrderData
      .Request(new ValidationRequest(...))
      .Return())
{
  ...
}

.Request().Receive() bzw. .Request().Return() bastelt ein Standard-Request-Object zusammen und steckt einen dynamischen Antwort-Pin hinein. So kann die Sequenz der Verarbeitungsschritte in einer Methode notiert werden.

So wie gezeigt setzte das sync EBC voraus. Async funktioniert es nur geschachtelt:

this.Out_GetCustomerById
  .Request("123")
  .Receive(c => {
    ...
    this.Out_ValidateOrderData
      .Request(new ValidationRequest(...))
      .Receive(isValid => {
        if (isValid)
        {
          ...
        }
      });
  });

Ob das die beste Art ist, einen Workflow/Prozess mit EBC zu notieren, sei mal dahingestellt. Das Beispiel ist auch etwas künstlich. Es gibt auch noch andere Möglichkeiten mit Iteratoren.

Hier wollte ich aber zeigen, dass die ad hoc Antwort-Pins etwas überhaupt möglich machen, was feste nicht können.

Außerdem hat man bei EBCs immer den Overhead in Form der Doppelung der Anzahl der Member in Interface und Komponenten (durch die Aufteilung von Methoden mit Rückgabewert in Input- und Output-Pin) überall.

Das ist, wie gezeigt, nicht so einfach wahr. Es geht 1. mit einem Request/Response Pin und 2. ist die Modellierung von EBC durchaus anders. Die Rechnung "IBC Funktion führt bei EBC zu zwei Pins" verkennt, dass EBC anders modelliert werden als IBC.

Dadurch, dass EBC Flüsse modellieren, sind sie weniger darauf aus, einen Sender einer Query auch gleich zum Empfänger des Resultats zu machen.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ralfw,

Das ist, wie gezeigt, nicht so einfach wahr.

aber falsch ist es auch nicht. 😃

Es geht 1. mit einem Request/Response Pin

Gehen tut es, aber das ist nicht der standardmäßig vorgeschlagene Weg. Der sieht eine Aufteilung von Parametern und Rückgabewerten vor in zwei Pins vor.

und 2. ist die Modellierung von EBC durchaus anders. Die Rechnung "IBC Funktion führt bei EBC zu zwei Pins" verkennt, dass EBC anders modelliert werden als IBC.

Klar ist die anders, aber inwiefern ändert das was an der Verdoppelung? In meinem Compiler-Beispiel, das ich in EBC: Validierung und Exception Handling gepostet hatte, kommt es jedenfalls praktisch und faktisch zu einer Verdoppelung und ich sehe nicht, wie man da durch einen andern Fluss was noch einsparen könnte. Der Fluss ist schon minimal.

Dadurch, dass EBC Flüsse modellieren, sind sie weniger darauf aus, einen Sender einer Query auch gleich zum Empfänger des Resultats zu machen.

Ja, genau das habe ich ja eben an die Adresse von ErfinderDesRades gerichtet geschrieben, als ich argumentiert habe, warum ich Getter-Drähte für ungünstig halte. Das sollte zeigen, dass mir das mit dem Fluss durchaus bewusst ist.

herbivore

71 Beiträge seit 2010
vor 13 Jahren

Gehen tut es, aber das ist nicht der standardmäßig vorgeschlagene Weg. Der sieht eine Aufteilung von Parametern und Rückgabewerten vor in zwei Pins vor.

Woher hast du denn das? Wer hat das als "nicht standardmäßig" definiert? ECMA, ISO, DIN, Martin Fowler, Gregor Hohpe?

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ralfw,

Wer hat das als "nicht standardmäßig" definiert?

du, wenn auch nicht durch eine formale Definition, sondern durch die Mehrzahl der Beispiele, die ich von dir gesehen habe. Zwar hast du dich in deinem Blog auch damit beschäftigt, wie man das Verbinden vereinfachen kann, aber soweit ich das überblicke, gibt es da noch kein Ergebnis, von dem du gesagt hast: Lasst die Verdoppelung sein, sondern macht es in Zukunft immer so und so. Ist es den wirklich so abwegig zu sagen, dass die Verdoppelung zum jetzigen Zeitpunkt der Normalfall ist?

herbivore

71 Beiträge seit 2010
vor 13 Jahren

Lasst die Verdoppelung sein, sondern macht es in Zukunft immer so und so. Ist es den wirklich so abwegig zu sagen, dass die Verdoppelung zum jetzigen Zeitpunkt der Normalfall ist?

Tut mir leid, dass es da ein Missverständnis gibt. Ich ziehe nicht das eine dem anderen vor. Sowenig wie ich Prozeduren Funktionen vorziehen oder umgekehrt. Es gibt Gelegenheiten, da sollte man für Request/Response explizite, getrenne, von außen sichtbare Pins benutzen. Bei anderen Gelegenheiten reicht ein Pin, auf dem eine Query-Nachricht mit "lokalem" Antwort-Pin verschickt wird.

Die Entkopplung von Versand und Empfang finde ich zwar flexibler. Aber nicht immer ist das die beste Ausdrucksform. Einen Input-Pin für den Empfang eines Resultats modelliere ich, wenn ein Resultat durchaus auch ohne Initiative eines Senders ankommen kann.

Hört sich merkwürdig an, ist aber häufig der Fall. Beispiel: Eine GUI-Komponente kann eine Query absetzen, um Auftragsdaten für die Anzeige auszuwählen. Wäre nur das eine Anforderung, dann wäre eine Modellierung mit einem Query-Pin ok.

Aber was, wenn Auftragsdaten auch ohne Query ankommen können? Das kann z.B. beim Start der GUI-Komponente sein; sie könnte dann die letzten 10 Aufträge anzeigen, die eingegangen sind. Oder es könnte sein, dass die Auftragsliste autom. aktualisiert werden soll.

Es gibt also schon einige Fälle, wo man gemeinhin "in einer Funktion denkt" (IEnumerable<Auftrag> FindeAufträge(Auftragsquery q)) - aber es wäre eigentlich besser, Frage und Antwort zu trennen.

"Immer so" (immer 2 Pins für Request/Response oder immer 1 Pin) gibt es für mich aber nicht. Beide Formen sind wertvoll.

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

@ralf:

Potzblitz, das ist jetzt das 2. mal, dass du mich von was übezeugt hast, von dem ich gar nicht überzeugt war. 👍
Man kann tatsächlich mit deinen trickreichen Request-Extensions die EBC-Signaturen dermaßen austricksen, dass man mit einem look and feel returnender Events arbeiten kann.
Man muß keine speziellen Delegaten definieren oder gar Request-Klassen implementieren und instanzieren - das geht komplett generisch.

Das Event


      public event Request<int, string> GetNameByID;

ist kein Deut komplexer als die returnende Alternative


      public event Func<int, string> GetNameByID2;

und die Unterschiede inne Benutzung sind wirklich marginal


      public void CallGetNameByID() {
         var name = GetNameByID.Evaluate(99);
         name = GetNameByID2(99);
      }

Wenn man das so will.
Dass du davon abrätst, weil es das zugrundeliegende Callback-Konzept aussehen läßt wie ein Return-Konzept nehme ich zur Kenntnis.
Stimmt schon, verschleiert ist die von vornherein gegebene Async-Fähigkeit des Callback-Konzepts, und auch das Feature, dass man den callback ja problemlos umlenken kann.
Aber es ist so _:::

Und ein asymetrischer bidirektionaler Pfeil ist ein Stück aussagekräftiger als 2 unidirektionale, und außerdem hat man weniger Kram im Diagramm herumfahren.

Tja, mir sind die Argumente ausgegangen, für Getter- und gegen bidirektionale Drähte - dann sollichwohl mal die Klappe halten

🙂

Der frühe Apfel fängt den Wurm.

3.728 Beiträge seit 2005
vor 13 Jahren
1Pin oder 2 Pins?

Es geht 1. mit einem Request/Response Pin
Gehen tut es, aber das ist nicht der standardmäßig vorgeschlagene Weg. Der sieht eine Aufteilung von Parametern und Rückgabewerten vor in zwei Pins vor.

Natürlich schränkt es ein, wenn man Request/Response mit einem einzigen Pin abbildet, aber oft ist das genau das was man möchte. Wenn man die Antwortnachricht dann doch einmal an eine andere Komponente als die Aufrufer-Komponente senden will, dann kann man ja einen Splitter dazwischen stecken.

Ich sehe nicht ein, warum ich mit den doppelten Aufwand machen soll, wenn das im konkreten Fall keine Vorteile bringt.

Das heißt jetzt nicht, dass ich generell nur mit Func<Q,R> arbeite. Es gibt Fälle, bei denen macht Action<T> definitiv mehr Sinn.

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

@ralfw:

Jo, ich hab jetzt noch mitte Extensions rumprobiert, und Gründe gefunden, mich doch nochma zu wort zu melden 😉Hier propagierst du Callback-Drähte mit nur einem Parameter, indem der Callback in den Parameter gestopft wird.
In deim Blog, Abschnitt "Request/Response V2" dagegen übermittelst du den Callback als 2. Parameter.
Sollte man da nicht eine einheitlich Konvention anstreben?
Mir sagt das mit dem Extra-Parameter sehr zu, weil meist die Parametertypen primitiv sein können, aber wenn der Callback mit hinein muß, benötigt man für jeden Callback-Draht eine Extra-Instanz einer Extra-Klasse.
Bestimmt kann man einiges davon generisch machen, und auch mit Extensions versüßen, aber ich glaub doch, dass der Code etwas komplizierter sein wird.
Und auch Konzept-theoretisch findichs sehr angemessen, einer Nachricht, die ja eiglich 2 Nachrichten ist, auch 2 Params zu spendieren.

Ich bezeichne bidirektionale Drähte übrigens jetzt als "Callback-Drähte", dassis kürzer, und Verschleierungen haben wirklich negatives Potential, zeigt sich grad wieder.

Nämlich im Blog schlägst du vor, einen eigenen Request-Delegaten zu definieren, für den man dann Extensions schreiben kann. Der sieht auch zunächst viel netter aus, als das Konstrukt, was wirklich dahinter steht:


Request<TIn, TOut>

sieht netter aus als


Action<TIn, Action<TOut>>

Beim Modellieren eines Callback-Out-Pins sieht Request auch besser aus:


      public event Request<int, string> GetNameByIDr;

statt


      public event Action<int, Action<string>> GetNameByID;

Aber wennich jetzt den zugehörigen In-Pin definiere, passt der scheinbar nicht mehr zum zugehörigen Out-Pin, und ich muß mich doch am Delegaten orientieren, um die In-Pin-MethodenSignatur richtig hinzukriegen.
Vgl. die Paarungen


      public event Request<int, string> GetNameByID;   //Out
      public void GetNameByID(int id, Action<string> callback) { }  //In

und


      public event Action<int, Action<string>> GetNameByID;   //Out
      public void GetNameByID(int id, Action<string> callback) { }  //In

Bei der 2. Paarung kannich ganz schematisch vonne Event-Signatur auf die Methoden-Signatur schließen, bei der 1. Paarung hingegen mussich mir die Signatur eines Requests in Erinnerung rufen.
Und dann siehts immer noch komisch un-zusammenpassend aus, findich.

Jedenfalls deine Extensions habich für Action<TIn, Action<TOut>> umgeschrieben, und auch welche für reine Pull-Callbacks gebastelt - das war ja mein ursprüngliches Anliegen: ich wollte ja nur auch Pullen können, aber Callback-Drähte decken das fabelhaft mit ab, wennmanweißwie.

namespace System {

   public static class RequestX {
      /// <summary>
      /// executes the callbackCaller with a generated callback, which evaluates the result.
      /// Note: this way of pulling values is comfortable, but abstains from advantaged concepts of
      /// Callback-EBC-wires: redirectable callbacks and asynchronous execution
      /// </summary>
      public static TOut Evaluate<TOut>(this Action<Action<TOut>> callbackCaller) {
         TOut result = default(TOut);
         callbackCaller(r => result = r);
         return result;
      }
      /// <summary>
      /// executes the callbackCaller with a generated callback, which evaluates the result.
      /// Note: this way of pulling values is comfortable, but abstains from advantaged concepts of
      /// Callback-EBC-wires: redirectable callbacks and asynchronous execution
      /// </summary>
      public static TOut Evaluate<TIn, TOut>(this Action<TIn, Action<TOut>> callbackCaller, TIn inArg) {
         TOut result = default(TOut);
         callbackCaller(inArg, r => result = r);
         return result;
      }
      /// <summary>
      /// publishes another Name to invoke the delegate, in order to make more clear, what happens
      /// </summary>
      public static void Callback<TOut>(this Action<Action<TOut>> callbackCaller, Action<TOut> callback) {
         callbackCaller(callback);
      }
      /// <summary>
      /// publishes another Name to invoke the delegate, in order to make more clear, what happens
      /// </summary>
      public static void Request<TIn, TOut>(
            this Action<TIn, Action<TOut>> request, TIn inArg,Action<TOut > callback) {
         request(inArg, callback);
      }
      /// <summary>
      /// flowlayout-entrypoint for syntax like:  GetNameByID.Request(66).Callback(r => name = r);
      /// </summary>
      public static Continuation<TOut> Request<TIn, TOut>(
            this Action<TIn, Action<TOut>> request, TIn inArg) {
         return new Continuation<TOut>(r => request(inArg, r));
      }
      public class Continuation<TOut> {
         public readonly Action<Action<TOut>> Callback;
         public Continuation(Action<Action<TOut>> callbackCaller) {
            this.Callback = callbackCaller;
         }
      }
   }
}


namespace Request3 {
   using System;

   public class Client {
      public event Request<int, string> GetNameByIDr;
      public event Action<int, Action<string>> GetNameByID;
      public event Action<Action<string>> GetRandomName;

      public void TestAccesses() {
         //requests
         var name = GetNameByIDr.Evaluate(99); //callback-syntax per extension in return-syntax verformt
         GetNameByID.Request(66).Callback(r => name = r);  //flowlayout-syntax verdeutlicht callback-Konzept
         GetNameByID.Invoke(44, r => name = r);           //original-syntax

         //pulls
         name = GetRandomName.Evaluate();  //callback-syntax per extension in return-syntax verformt
         GetRandomName.Request().Callback(r => name = r); //flowlayout sieht bei pull beknackt aus
         GetRandomName.Callback(r => name = r);      //dummi-Extension verdeutlicht callback-Konzept
         GetRandomName.Invoke(r => name = r);            //original-syntax
      }
   }
   public class Server {
      public void GetNameByID(int id, Action<string> callback) {
         callback("Rumpelstilzchen" + id);
      }
      public void GetRandomName(Action<string> callback) {
         callback("Rumpelstilzchen"+Environment.TickCount);
      }
   }
   public class Mainboard {
      public Mainboard(Server server, Client client) {
         client.GetNameByIDr += server.GetNameByID;
         client.GetNameByID += server.GetNameByID;
         client.GetRandomName += server.GetRandomName;
         client.TestAccesses();
      }
   }
}

Der frühe Apfel fängt den Wurm.

742 Beiträge seit 2005
vor 13 Jahren

Man kann es auch überspezifizieren denke ich.

Ob das jetzt ein Parameter ist oder 2 Parameter mit Rückgabetyp ist doch wirklich egal. Du musst dich halt im Falle eines Designers an seine Vorgaben beugen bzw. den Generierungsprozess anpasssen aber das ist ja normal.

71 Beiträge seit 2010
vor 13 Jahren

Leider weiß ich nicht so recht, was du mir sagen willst, ErfinderDesRades.

Mein Ansatz für "zusammengehöriges" Request/Response war dieser:

class Request<TRequest, TResponse>
{
    public TRequest Data;
    public Action<TResponse> ResponsePin;
}

interface IService
{
    void In_Process(Request<string, int> req);
}

interace IClient
{
    event Action<Request<string, int>> Out_Process;
}

Und deiner ist dieser:

delegate void Request<TRequest, TResponse>(TRequest data, Action<TResponse> responsePin);

interface IService
{
    void In_Process(string data, Action<int> responsePin);
}

interface IClient
{
    event Request<string, int> Out_Process;
}

Das ist nett. Aber ist das ein großer Sprung nach vorn?
Wie ich ja schon hier beschrieben habe, können EBC-Komponenten auch Methoden sein.

Mit Gettern hat das nun aber nicht mehr wirklich etwas zu tun.

Wie gesagt: Ist doch alles ok. Ändert am Prinzip am nicht wirklich etwas. Empfänger definieren keine Properties. Und es gibt kein reines Pull (im Sinne einer Abfrage einer Property).

Nachteilig bei Ansatz mit dem Response-Pin als separater Parameter ist, dass damit die Zahl der Signaturen nun nicht mehr begrenzt ist. Denn wenn man nicht nur eine Antwort, sondern vielleicht auch noch Notifikationen (Fortschrittsanzeige) oder Exeptions als "Rückgabewerte" modellieren will, dann ändern sich die Signaturen ständig.

void In_Process(string data, Action<int> responsePin);
void In_Process(string data, Action<int> responsePin, Action<double> progressPin);
void In_Process(string data, Action<int> responsePin, Action<double> progressPin, Action<Exception> exceptionPin);

Bei Beschränkung auf einen Param ist die grundsätzliche Signaturstruktur aber immer gleich:

void In_Process<T>(T request);

Wobei T ein Typ sein kann, der nur Daten oder Daten+Response-Pin oder Daten+ResponsePin+ProgressPin usw. transportieren kann.

Das halte ich für einen Vorteil beim Tooling. Das heißt aber nicht, dass man es garreinnie anders machen darf. Man muss nur verstehen, was Pins in der Signatur einer Methode für Konsequenzen haben.

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

Ähm, beide Ansätze sind von dir. Was du als "mein" Ansatz bezeichnest, ist, wie gesagt, von deinem Blog, Abschnitt "Request/Response V2".

Hmm, sieht aber so aus, das dieser Ansatz verworfen werden wird.

Weil IMO _muß _ einer der beiden verworfen werden, da ansonsten verschiedene Programmierer Komponenten erstellen, deren Callback-Pins nicht zueinander passen. (dassis glaub, wassichdir sagen wollte)

Wie ich ja schon hier beschrieben habe, können EBC-Komponenten auch Methoden sein.

Findich hochinteressant, habich auch schon paar Fragen zu. Nur verstehe ich nicht die Verbindung zur Diskussion hier, nämlich wie Callback-Drähte konzipieren.

Der frühe Apfel fängt den Wurm.