Follow my new blog

Samstag, 23. April 2011

Lernkartei III – Vom Stapel lernen

Die Lernkartei “zuckt schon”, wie der vorherige Artikel beschrieben hat. Als Anwender kann ich im Lernmodus Karteikarten “durchblättern”, also schon beurteilen, ob mir Darstellung und Interaktionen gefallen. Lernen, im Sinne einer Wiedervorlage von nicht gewussten Antworten, kann ich mit dem Programm allerdings noch nicht. Das soll in der zweiten Iteration hier nun nachgerüstet werden.

Arbeiten mit der Lernkartei

Bevor ich mich aber in die Modellierung stürze, muss ich die Anforderungen verstehen. Was bedeutet denn das Lernen mit Karteikarten konkret, wie gehe ich dabei vor? Wie soll sich das Programm verhalten?

Struktur der Lernkartei

Ich stelle mir das so vor:

image

Die Karten, die ich lerne, nehme ich von einem Stapel (Batch). Der enthält für einen Stapeldurchlauf (Batch Run) nicht zuviele Karten.

Gefüllt wird der Stapel aus Fächern (Compartment). Es gibt n+1 Fächer, von denen das 0-te eine besondere Bedeutung hat. Das 0-te Fach ist die Halde (Heap). Zu Beginn des Lernens mit einer Lernkartei liegen alle Karteikarten in der Halde.

Wenn ich frische Karteikarten für den Stapel brauche (weil in den anderen Fächern gerade keine zum Lernen anstehen), nehme ich sie von der Halde. Und vom Stapel wandern sie in die Fächer 1..n. Dazu später mehr.

Eine Karteikarte, deren Antwort ich weiß, die aus dem Fach n kommt, geht schließlich ins Archiv (Archive). Sie verlässt damit die Fächer und wird nicht wieder vorgelegt.

Lernen ist mithin der Prozess, der die Halde ins Archiv transferiert.

Lernalgorithmus

Die Karten, die ich lernen will, liegen auf dem Stapel. Der sollte nicht zu hoch sein, damit nicht gewusste Karten immer wieder mal angeboten werden. Ich sag mal, mehr als 20-25 Karten liegen nicht auf dem Stapel.

Vom Stapel lerne ich solange, bis nur 3 Karten darauf sind. Denn ab 3 Karten ist die Wiedervorlage nicht gewusster Karten so zügig, dass sie mir beim Lernen nicht mehr wirklich hilft.

Lernen vom Stapel bedeutet:

  1. Ich nehme die oberste Karteikarte und schaue mir die Frage an.
  2. Dann schaue ich mir die Antwort an und beurteile, ob ich sie gewusst habe.
    • Wenn ich die Antwort gewusst habe, stecke ich die Karte ein Fach weiter. Jede Karte kommt aus einem Fach auf den Stapel; ich weiß also, welches Fach für sie dann das nächste Fach ist. Gewusste Karten wandern so von Fach zu Fach bis ins Archiv.
      Karte aus Fach f kommt auf den Stapel und wenn ich ihre Antwort weiß, vom Stapel in f+1 (bzw. ins Archiv).
    • Wenn ich die Antwort nicht gewusst habe, stecke ich die Karte ans Ende des Stapels. Sie “blubbert” dann langsam wieder an seine Oberfläche, so dass ich mich mit ihr früher oder später wieder beschäftige.
      Karten aus Fach f, deren Antwort ich nicht weiß, verlieren ihre Herkunft und werden zurückgestuft auf Fach 1.
  3. Wenn ich mit dem Lernen aufhöre und noch Karten auf dem Stapel sind, kommen die zurück in Fach 1.

image

Wenn der Stapel am Anfang des Lernens leer ist oder immer wenn er während des Lernens die minimale Anzahl an Karten erreicht, fülle ich ihn aus den Fächern wie folgt:

  1. Alle Karten, die in Fach 1 sind, kommen auf den Stapel.
  2. Wenn noch Platz auf dem Stapel ist, dann fülle ich ihn mit Karten aus dem letzten Fach, das voll ist. Die Fächer haben eine steigende Kapazität, damit Karten immer seltener zum Lernen vorgelegt werden. Das ist ja der Trick am Lernen mit der Lernkartei. Fach 1 hat eine Kapazität wie der Stapel k1=ks. Die weiteren Fächergrößen verdoppeln die Kapazität, k2=40, k3=80, k4=160, k5=320.
    Ich schaue also zuerst, ob Fach n voll ist, wenn nicht, dann ob Fach n-1 voll ist usw. Fach 1 ist zu diesem Zeitpunkt immer leer (s. Schritt 1). Aber Fach 0, die Halde, ist immer voll, egal wieviele Kartei noch auf Halde liegen.
    Dieses Vorgehen sichert zu, dass volle Fächer langsam abgearbeitet werden und dass immer wieder neue Karten von der Halde “ins Spiel kommen”. Schonmal gewusste Karten (in vollen Fächern) haben also Vorrang vor Karten von der Halde.
  3. Falls der Stapel immer noch nicht gefüllt ist (weil die Halde leer ist und kein anderes Fach voll), wird er aus den Fächern in der Reihenfolge 2..n bestückt.

Ich finde den Umgang mit der Lernkartei (Flash Card Box) in dieser Weise geradlinig. Gelernt werden die Karten, die ich sozusagen in der Hand halte (Stapel) und gespeist wird der Lernstoff aus dem Karteikasten, d.h. den Karten, die ich schonmal in der Hand hatte, oder der Halde. Wenn ich neuen Lernstoff brauche, greife ich einfach in die Fächer.

Flow modellieren

Bisher sieht der Flow für das Lernen so aus:

image

Die Karten, die am Ende herauskommen, sind “Zufallsprodukte” von Get next card.

Ab jetzt sollen die Karten jedoch vom Stapel kommen. Advance card muss die aktuelle Karte entweder unter den Stapel schieben (wenn Antwort nicht gewusst) oder vom Stapel nehmen und ein Fach weiter stecken. Und Get next card muss die nächste Karte vom Stapel holen bzw. ggf. den Stapel neu füllen.

Beide Funktionseinheiten müssen deshalb den Stapel kennen:

image

Die kleine Tonne an den Funktionseinheiten ist die Kurzschreibweise für eine Abhängigkeit.  Advance card und Get next card sind also abhängig vom Stapel, sie haben damit gemeinsamen Zustand.

Fragt sich jetzt nur, wie der Stapel initial gefüllt wird und wie die erste Karte bei Start der Anwendung in den View bzw. das ViewModel kommt. Dafür ist ein “Nebenfluss” nötig:

image

Open box öffnet die Lernkartei (Flash Card Box) und sorgt dafür, dass Get next card die erste Karte vom Stapel ans ViewModel schickt.

Achten Sie auf das (C) bei Open box, es zeigt an, dass die Funktionseinheit in der Config-Phase des Programmstarts ausgeführt wird. Zu dem Zeitpunkt sind alle Funktionseinheiten erzeugt, gebunden und mit ihren Abhängigkeiten versorgt (Phasen Build, Bind, Inject).

Auf die Config-Phase folgt dann die Run-Phase, die eine ausgezeichnete Funktionseinheit startet, so dass es auch für den Anwender losgeht.

image

Der View selbst ist diese ausgezeichnete Funktionseinheit, der EntryPoint für die Flows.

Feature Slicing

Da ich noch keine echten Karteikarten habe, füllt Open box die Lernkartei mit Dummy-Karten. Im Fokus dieser Iteration ist das Vorgehen beim Lernen; dafür brauche ich als Anwender noch keine echten Karten, sondern muss nur beurteilen können, ob das Programm gem. Algorithmus mit der Lernkartei umgeht.

Für diese Iteration specke ich sogar noch weiter ab. Das Programm soll noch nicht einmal den ganzen Lernalgorithmus implementieren, sondern nur das Lernen vom Stapel. Die Karten werden also noch nicht aus Fächern geholt und auch nicht weitergesteckt.

Aus dem ganzen Feature “Lernen nach Lernalgorithmus” schneide ich mir nur eine dünne Scheibe (Feature Slice), um schneller etwas auf die Straße zu bekommen. Das Modell erfährt dadurch schon eine wichtige Erweiterung (es kommt Zustand hinzu, der Zustand wird initialisiert, der ganze Programmstart bekommt mehr Systematik) und als Anwender habe ich einen überschaubaren sowie schnell überprüfbaren Nutzenzuwachs.

Daten modellieren

Bisher waren die Daten, die da im Flow flossen, sehr einfach. Die Karte enthielt nur zwei Felder: Frage und Antwort. Dazu war nicht viel zu sagen. Doch jetzt kommt einiges hinzu: Stapel, Fächer, Archiv. Ein explizites Datenmodell lohnt sich daher:

image

Die Flash Card Box ist die Spinne im Netz. Sie zieht Stapel, Fächer und Archiv zusammen. Und mehr nicht.

Alle Datenfunktionseinheiten haben möglichst simple, auf den hiesigen Zweck zugeschnittene Schnittstellen.

Für diese Iteration brauche ich allerdings nur die Flash Card Box, Batch und Batch Card. Card habe ich schon.

Als Notation für das Datenmodell habe ich bewusst die “Krähenfußnotation” gewählt (mit etwas API-Zucker oben drauf). Ich stimme nämlich Jim Stewart zu, dass die am leichtesten verständlich ist.

Ebenfalls bewusst habe ich im Diagramm auch Funktionseinheiten wiederholt (Card). Viele Linien, die alle auf den selben Kasten weisen, finde ich verwirrend. Sie machen das Verstehen von Diagrammteilen schwieriger und suggerieren Abhängigkeiten, wo keine sind.

Feature Slice implementieren

Plan a little, code a little. So geht die Implementierung für das Feature Slice leicht von der Hand. An den Kontrakten ist nur wenig zu machen:

image

Und der Code für die Datentypen ist ganz einfach, weil er im Grunde nur eine Queue kapselt. Spannender ist da schon die Implementierung für die Aktionen Advance card usw. Die sind ja nun zustandsbehaftet:

image

Da schien mir ausnahmsweise mal eine Ableitung angebracht. Alle Aktionen, die sich eine FlashCardBox als Instanz teilen, erben von FlashCardBoxEntity. Die Klasse implementiert IDependsOn<T> und enthält eine Variable für den Zustand:

image

So werden die Ationen übersichtlicher, weil sie sich aufs Wesentliche konzentrieren:

image

Die Initialisierung erfolgt in der Startup-Phase Inject:

image

Wenn Sie genau hinschauen, sind die Aktionen jedoch nicht direkt von FlashCardBox abhängig, sondern von SharedState<FlashCardBox>. Warum das? Weil nur so es möglich ist, dass Open box für alle anderen den Zustand erzeugt und setzt.

image

Natürlich hätte außerhalb eine Instanz von FlashCardBox erzeugt und in alle injiziert werden können, doch dann hätte die einen parameterlosen Ctor haben müssen. Das fand ich unschön. Mit dieser Lösung jedoch ist es erstens möglich, die Lernkartei-Instanz auszutauschen und zweitens die Initialisierung mit einem Ctor sehr schön in einer Aktion zu kapseln.

Zwischenstand

Der Code ist wie immer im Mercurial Repository zu finden: http://code.google.com/p/wpfflashcards/

Mir hat diese Iteration Spaß gemacht. Der entsprang besonders der Auflösung einer Spannung, in die ich mich hineinmanövriert hatte. Zu Anfang hatte ich nämlich geplant, das komplette Feature zu implementieren. Ich hatte es auch modelliert. Aber dann… war mir die Zeit zur Implementierung zu knapp. Ich fühlte mich unwohl. Sollte ich versuchen, alles huschhusch runterzucoden?

Doch dann habe ich mich zum Feature Slicing entschieden. Warum nicht aus dem Gesamtmodell für das Lernen nur den Stapel herauslösen und implementieren? Ja, warum eigentlich nicht? Wenn unter Druck, dann ist das ein probates Mittel, ihn zu reduzieren: einfach die Nutzenscheibe dünner schneiden. Das ist viel besser, als mit dem ganzen Feature anzufangen und nicht fertig zu werden.

Aus Anwendersicht ist nicht soviel auf die Straße gekommen, doch das, was da ist, ist solide gemacht. Das Modell ist sauber und zukunftstauglich. Und die Implementierung ist durch Tests gestützt.

Aus Entwicklersicht kann ich zufrieden sein, weil ich Nutzen geliefert habe. Und gleichzeitig habe ich das Modell insgesamt runder gemacht, weil nun ein vernünftiger Anfang (Open box) für die Daten da ist. Die muss ich nun nicht mehr wie in der ersten Iteration in den Flow “hineinmogeln”.

Mal schauen, was beim nächsten Mal dran ist. Wahrscheinlich werde ich das Lernen komplettieren mit Halde, Fächern und Archiv.

Kommentare:

tanascius hat gesagt…

Hallo Ralf,
ich habe Schwierigkeiten, dir zu folgen, was passieren soll, wenn der Stapel leer ist. Du schreibst da Sätze wie:
"Aber Fach 0, die Halde, ist immer voll, egal wieviele Kartei noch auf Halde liegen."
oder
"Falls der Stapel immer noch nicht gefüllt ist (weil die Halde leer ist und kein anderes Fach voll), wird er aus den Fächern in der Reihenfolge 2..n bestückt.".

Jetzt ist die Halde doch leer ... und warum sollten die Fächer denn nicht auch leer sein? Was hilft es dann sie 2..n durchzugehen?

Vielleicht kannst du das noch etwas deutlicher beschreiben.
Danke!

Ralf Westphal - One Man Think Tank hat gesagt…

@Tanascius: Es gibt für Fächer drei zustände: Leer, gefüllt und voll.

Leer ist leer. Voll ist aber nicht einfach nur nicht leer, sondern eben voll, d.h. es passt nix mehr rein ins fach. Gefüllt ist alles zwischen leer und voll.

Die Halde ist besonders: Sie ist entweder leer oder voll. D.h. von der Halde können immer Karten auf den Stapel wandern in Schritt 2, um ihn aufzufüllen. Von anderen Fächern nicht.

Sind andere Fächer nicht voll, wandern nur Karten auf den Stapel, falls die Halde leer ist.

Wenn die Halde leer ist, dann sind alle Karten "im Spiel", also entweder in einem Fach oder im Archiv. Die Fächer sind dann also nicht zwangsläufig leer.

Florian Mätschke hat gesagt…

Lern-Rauschen:
Hallo Ralf!
Ich finde deine Beispiele sehr sauber und übersichtlich was die Umsetzung / Planung angeht. Allerdings erzeugt die "Anforderung" mit der Box teils ein gewisses Rauschen beim lernen/verstehen der Vorgehensweise mit Flow-Design/EBC und deren Evolvierbarkeit, wie ich finde. - Vermutlich wird dies in späteren Kapiteln im Flow-Design besser verständlich und übersichtlicher.
Kurz,was ich sagen will:
Auch wenn das Prinzip mit dem Lernkasten klar ist, sind manche Anforderungen (Mechanismen) schwer auf Anhieb zu begreifen. Wie dem auch sei....


Ich habe in dieser Iteration übrigens mit einem Kollegen erstmals Flow-Design produktiv eingesetzt (gestern), mit dem Wissen, dass ich aus deinem letzten Vortrag und Wissen aus der dotnetPro hatte. Mein Kollege kannte weder Flow-Design noch EBC.
Heute entwickelt er bereits in EBC, was wir gestern Nachmittag in FD entworfen haben. - Er ist begeistert, wie klar und schnell es geht.

Anfangs noch ein paar Probleme mit dem Binden, aber das geht schnell.

Was ich noch fragen muss: Mir ist aufgefallen, du verwendest bei den Funktionseinheiten immer nur Process() und Continue(). Hat das gewollte Vorteile?

Was ist, wenn ich 2 oder mehr Eingänge habe? Process1(), Process2()?

Danke!

Grüße,
Florian

Ralf Westphal - One Man Think Tank hat gesagt…

@Florian: "Allerdings erzeugt die "Anforderung" mit der Box teils ein gewisses Rauschen beim lernen/verstehen" - Das versteh ich nicht recht. Du meinst, ich sollte die Anforderungen nicht erklären, sondern nur das Modell präsentieren?

Das möchte ich aber nicht. Dann fällt das Modell vom Himmel und keiner weiß, warum es so aussieht. Ich denke, es ich wichtig, die Verbindung zwischen Anforderungen und Modell zu sehen. Deshalb muss ich die Anforderungen erklären.

Freut mich, dass du mit deinen Kollegen einen einfachen Einstieg in Flow-Design gefunden hast.

Process und Result bzw Continue sind Standardnamen, wenn eine Funktionseinheit nur einen Input und einen Output hat. Das ist bei Aktionen als Funktionseinheiten allermeistens der Fall.

Bei Akteuren als Funktionseinheiten ist es anders; die haben oft viele Ein-/Ausgänge. Denen kannst du dann zweckspezifische Namen geben.

Florian Maetschke hat gesagt…

Hallo Ralf!

Danke für die Hinweise zu Aktionen/Akteuren bei Funktionseinheiten.

Ich meinte nicht, dass das Model weggelassen werden sollte.
Viel mehr meinte ich, dass das Model schwerer verständlich ist, als z.B. eine Ampelschaltung an einer Kreuzung mit Fußgängerüberweg.
Da weiß sofort jeder, was damit gemeint war.

Vielleicht liegt es aber auch einfach nur daran, dass ich nie mit dem Prinzip Vokabeln gelernt habe.. hehe:)


Bin gespannt auf Teil 4

Ralf Westphal - One Man Think Tank hat gesagt…

@Florian: Ampelschaltung wäre vielleicht einfacher. Aber das ist es ja eben: Ich wollte nicht wieder so einfach. Flow-Design soll mit einem anspruchsvolleren Szenario demonstriert werden. Einfach kann jeder und ist schon oft gezeigt worden.

Und wenn du bisher nicht so recht Vokabeln gelernt hast, dann ich mit dieser Anwendung endlich der richtige Zeitpunkt, damit anzufangen :-) (Aber du musst gar keine Vokabeln damit lernen; alles mögliche lässt sich mit Karteikarten lernen.)

Anonym hat gesagt…

Hallo Ralf

Danke für das Sample.

Zunächst einmal enspricht die Namensgebung der Klassen teilweise nicht den Microsoft Konventionen.

Beispiele:
Map_card_to_learningVM
Fill_batch_from_compartment

Gross- und Kleinschreibung und Verwendung von Underscore.

Missbrauch der CodeBehind Funktionalität von VS bezüglich FlahsCardBoxEntity und Unterklassen.

Bei WPF- und WinForms Applikationen habe ich die Erfahrung gemacht, dass der MVVM- und MVP-Pattern sehr sinnvoll ist.

Wo ist in diesem Sample das Model? Entities sind Datenklassen und eigentlich keine Modelle.

Das LearningViewModel (entspricht Presenter) sollte keine Properties wie Answer oder Question enthalten. Die Properties sollten in ein Model ausgelagert werden.

Die Benennung der Klasse FlashCardBoxEntity bezüglich Entity ist irreführend (Datenklassen, EntityFramework). Vielleicht wäre Activity oder Action besser, als Entity.

Diese Anwendung wäre womöglich einfacher per Workflows zu meistern.

Grundsätzlich ist diese Idee, Komponten per EBC zu erstellen, die vollständig entkoppelt sind,
sehr gut. Der Aufwand scheint mir sehr viel höher zu sein, als bei Interface Based Components.

Mit den besten Grüssen.

Stefan Werdenberg, Schweiz
st.werdenberg@bluewin.ch

Ralf Westphal - One Man Think Tank hat gesagt…

@Stefan:

zur Namensgebung: Ob Namensgebung einer Microsoft-Konvention entspricht, finde ich nicht wichtig. Wichtiger ist, ob sie zu verständlichen Namen führt. Und da sage mir jmd, dass "Fill_batch_from_compartment" schlechter lesbar als "FillBatchFromCompartment" oder "Fillbachfromcompartment" ist.

Wenn du allerdings darauf abzielt, die Bennung von Klassen durch "Verbphrasen" zu kritisieren, dann verliert dein Argument jede Bedeutung, weil es ja bei EBC genau darum geht, Klassen als Aktionen und nicht Substantive zu sehen.

zu Missbrauch von Code Behind: Bitte zeige mir die Gebrauchsregeln für das, was du Code Behind nennst.

Es geht ja nicht um Code Behind, sondern um hierarchische Anordnung von Projektelementen. Das ist ein allgemeines Stil- bzw. Strukturierungsmittel, das Code Behind anwendet, das aber natürlich auch anderen Zwecken dienen kann. Einen Missbrauch kann ich nicht erkenntn, sondern nur eine kreative Nutzung eines VS Features.

zur Sinnfülle von MVVM und MVP: Es wäre schade, wenn dir entgangen wäre, dass ich MVVM implementiere.

zu "Wo ist das Modell": Welches Modell meinst du denn? Das ViewModel ist da, wo die Klasse ViewModel ist.

Und die FlashCardBox ist das Domänendatenmodell. Im Text ist das auch deutlich hervorgehoben.

zu Properties auf LearningViewModel: Wir reden hier über MVVM, deshalb ist dein Verweis auf Presenter leider unpassend.

Das Model ist die FlashCardBox, das ViewModel das LearningViewModel und der View der LeaningView. Wo ist die Abweichung von MVVM?

zur Verwendung des Begriffs Entity: Der EF hat den Begriff nicht gepachtet und ist auch ansonsten kein Ausbund an Unterstützung von sauberem Code.

Meine Verwendung von "Entity" für die Basisklasse folgt dem DDD. Schau mal in der Buch rein und sag mir dann, wo ich vom dortigen Konzept der Entity abweiche.

zu Workflows: Ich wäre sehr interessiert zu sehen, wie du die Anwendung mit WF realisierst. Mach das doch mal und dann vergleichen wir in puncto Evolvierbarkeit. Da können wir alle nur lernen. Wäre schön.

zur Höhe des Aufwands: Wenn du den Aufwand mit FD/EBC für höher hälst als mit IBC, dann wäre ich auch sehr interessiert daran, eine IBC Lösung zu sehen. Die sollte doch schnell zu machen sein, wenn es einfacher ist. Ich habe bisher ja nur vielleicht 0,5-1 Tag in die Programmierung investiert. Du solltest es also deutlich schneller hinkriegen. Wäre schön.

-Ralf

Anonym hat gesagt…

Lieber Ralf

Die EBCs sind auch bezüglich
der Steuerung eines Flows, sehr interessant, da diese Architektur nicht nur eine kleinere
Alternative zu WF ist, sondern darüber hinausgeht:
Im Bezug auf asynchrone Programmabläufe
verstehe ich die EBCs und Flow-Architektur und die eventbasierte Interaktion der Komponenten
dann ähnlich einem organischen Biosystem,
das "gleichzeitig" verschiedene Ereignisse bearbeiten oder an andere Organe (EB-Komponenten)
zur Bearbeitung weiterleiten kann.

Die EBC-Variante erscheint für komplexe Flows einfacher erstellbar und modularer als WF;
in der Denkweise herausragend und zukunftsorientiert
im Sinne der Adaption organischer natürlicher Vorgänge.

Eine eventbasierte Architektur ist überhaupt, sowie auch die OOP,
eine Adaption unserer eigenen menschlichen Wirklichkeit.

Bezüglich der Namensgebung. Ja, die Klassen müssen nur lesbar sein, dennoch finde ich die
MS Konventionen schön und richtig und sie haben den Sinn, dass andere Entwickler leichter
fremden Code lesen können.
Vielleicht könnten wir uns darauf einigen, dass "FlashCardBoxModel" in einem Ordner "Models"
zu einem schnelleren Verständnis Deines Samples führen würde. Zumal das
ViewModel ja auch LearningViewModel und der View LearningView heisst.
Womöglich denkst Du jetzt, der hätte ja auch einfach mal die Dokumentation
lesen können - stimmt.

Der MVVM Pattern ist eine kleine Abwandlung des MVP Patterns, wobei das ViewModel
(siehe Schematas bezüglich MVP / MVVM) den Presenter widerspiegelt und der Vergleich
ViewModel - Presenter deshalb keineswegs unpassend ist.

Unterdessen habe ich mich in Dein Sample besser eingearbeitet und sehe die
weitreichenden Überlegungen und Möglichkeiten, die darin vereint sind.

Ich danke Dir, dass ich etwas gelernt habe.

Mit den besten Grüssen.

Stefan Werdenberg

Marcus Linke hat gesagt…

Hallo Ralf,
erstmal vielen Dank für die Idee mit den EBCs.
Die Modelierung mit diesem Ansatz gefällt mir sehr gut. In der letzten Woche habe ich intensiv mit Deinen Blog Artikeln und den Artikeln in der dotnetpro zum Thema beschäftigt.
Sehr gut finde ich die Initiative mit der Lernkartei.
Hierzu habe ich eine Frage:
in wpffc.contracts befinden sich unter domaindata sowohl Klassen als auch Interfaces. Die Implementierung der Interfaces finde ich in der workbench für die flashcardbox. Soweit klar. Die Klassen Card und BatchCard sind mir nicht klar. Ich hatte die Contracte so verstanden, dass es generell Interfaces sein sollten.
Ich möchte an dieser Stelle keine Haarspalterei beterieben - ich würde nur gern Deinen Gedankengang verstehen, der zu dieser Form geführt hat.

Vielen Dank.

Grüsse Marcus

Ralf Westphal - One Man Think Tank hat gesagt…

@Marcus: Was in einem Kontraktassembly steht, muss nicht per se ein Interface sein. Kann auch eine Klasse sein, ein struct, ein enum. Whatever.

Voraussetzung ist eher, dass das, was dort steht, schnell hingeschrieben ist. Und dass es eine gewisse Reichweite haben soll.

Interfaces sind schnell hingeschrieben. Klassen mit Funktionalität nicht.

Datenklassen sind ok im Kontrakt. Aber die Flashcardbox hab ich da rausgenommen, weil die Implementationsdetails nur relativ lokal bekannt sein müssen. Card allerdings ist ein Datentyp für Nachrichten. Der fließt weiter und ist einfacher. Also finde ich eine Klasse im Kontrakt ok.

-Ralf

Mirko Schneider hat gesagt…

Moin Ralf,

zwei kleine Fragen dazu...
1. Das Tracen in Deinen EBCs kommt ja eigentlich einem Logging gleich. Logging wird überall als Cross-Cutting-Concern propagiert, und ein solcher schreit per Definition doch danach, in einer eigenen EBC realisiert zu sein. Ist es einfach Nachlässigkeit, dass Du den Belang nicht ausgelagert hast?
2. "var batchcard = batch.Dequeue();" ist dort eine Anweisung in Deiner Advance_card-Klasse. Warum ist diese Aktion nicht als EBC realisiert, die dann über seinen OutputPin die batchcard liefert?
Haut dies in die Kerbe der expliziten Abhängigkeiten bei EBC (Explizite Abhängigkeiten = Abhängigkeiten von Query-Response-Funktionalitäten bzw. Status-Objekten; und beide liefern Informationen per Rückgabewert statt Event per Definition???!?)

Ralf Westphal - One Man Think Tank hat gesagt…

@Mirko: Zu 1. Das Tracing findet absichtlich in den Funktionseinheiten statt. Es soll eine Spur hinterlassen, welche Funktionseinheiten durchlaufen werden. Das lässt sich nicht auslagern.

Tracing ist insofern kein Logging.

Zu 2. Nicht jede Anweisung muss ja in eine eigene Funktionseinheit. "Advance_card" ist die Aktion im Modell. Sie erfüllt aus Sicht der gewünschten Funktionalität einen Nutzen. Dass darin auf eine Queue zugegriffen wird, wie das Verhalten genau aussieht, das ist aus Modellsicht - finde ich - uninteressant, das muss nicht näher spezifiziert werden.