Ergebnis 1 bis 10 von 10

C# Design Pattern für Basisklasse mit unterschiedlichen Datentypen

  1. #1 Zitieren
    Ritter Avatar von Delta 38
    Registriert seit
    Nov 2008
    Ort
    Bremen
    Beiträge
    1.251
    Hallo zusammen, schön, dass es dieses Forum noch gibt

    Ich stehe vor folgender Problemstellung: Aus Nachrichten, die über eine beliebige Schnittstelle (Netzwerk, Dateien, ...) empfangen werden, soll jeweils ein entsprechendes Nachrichten-Objekt instanziiert werden. Der Aufbau aller möglichen Nachrichten ist genau gleich (Kopf, Inhalt, Fuß), die Datentypen der Inhalte sind jedoch unterschiedlich und stehen in keinerlei Zusammenhang. Eine Büchernachricht enthält beispielsweise eine Liste von Büchern, während eine Benutzernachricht den Namen und das aktuelle Kartenguthaben enthält. Der Inhalt der Nachricht lässt sich also nicht in einem Datentyp zusammenfassen, somit kann ich für die abstrakte Klasse oder das Interface auch keinen Rückgabewert für eine 'getInhalt()' Methode angeben.

    Zusätzlich soll die Art und Weise, wie der Inhalt der Nachricht geparst und die entsprechenden Objekte instanziiert werden, innerhalb der von Nachricht abgeleiteten Basisklasse befinden, damit man beim Empfangen einfach
    Code:
    Nachricht nachricht = NachrichtenParser.Parse(nachrichtenString);
    aufrufen kann. Je nach Nachrichtentyp möchte ich dann bei der Weiterverarbeitung die Liste von Büchern oder das Guthaben ausgeben.
    Code:
    Nachricht nachricht = NachrichtenParser.Parse(nachrichtenString);
    
    List<Buch> buecher = nachricht.getBuecher();
    double guthaben = nachricht.getGuthaben();
    
    if(buecher == null)
    ...
    Die beiden Methoden 'getBuecher()' und 'getGuthaben()' haben in der Basisklasse Nachricht jedoch meiner Meinung nach nichts zu suchen, wenn beispielsweise 100 Nachrichtentypen existieren müsste man ja 100 Funktionen haben, von denen nur eine einzige gebraucht wird (Deshalb würde ich auch das Visitor-Pattern ausschließen).
    Einfacher wäre es mit Downcasting
    Code:
    Nachricht nachricht = NachrichtenParser.Parse(nachrichtenString);
    
    if(nachricht is Buechernachricht)
        List<Buch> buecher = ((Buechernachricht)nachricht).getBuecher();
    ...
    Das ist aber wiederum sehr sehr unflexibel und widerspricht dem Open-Closed-Prinzip.

    Ich habe auch bereits mit dem Decorator-Pattern experimentiert
    Code:
    Nachricht nachricht = NachrichtenParser.Parse(nachrichtenString);
    
    BuecherNachrichtDecorator buchnachricht = new BuecherNachrichtDecorator(nachricht);
    List<Buch> buecher = buchnachricht.getBuecher();
    und in der Decorator-Klasse entsprechend
    Code:
    class BuecherNachrichtDecorator : Nachricht
    {
    	private Nachricht nachricht;
    	
    	public BuecherNachrichtDecorator(Nachricht msg)
    	{
    		nachricht = msg;
    	}
    	
    	public List<Buch> getBuecher()
    	{
                    if(nachricht is BuecherNachricht)
    		    BuchNachrichtContentParser contentParser = new BuchNachrichtContentParser();
    		    return contentParser.Parse(msg.getContentAsString());
    	}
    }
    Die Klasse Nachricht ist in diesem Kontext keine Basisklasse mehr und parst den Nachrichtenstring in Kopf, Inhalt und Fuß. Ein ContentParser wandelt dann den string in eine Liste von Büchern um. Man sieht jedoch, dass es prinzipiell das selbe nur in eine andere Klasse geschoben ist (Wieder wird über RTTI der Typ abgefragt). Sollte ich vielleicht nur den ContentParser aufrufen und wenn er den Inhalt nicht parsen kann null zurückgeben? Dann müsste ich jedoch alle vorhandenen Parser durchprobieren, bis einer was zurückgibt.

    Ich stehe hier auf dem Schlauch, daher meine Frage:
    Ich habe eine Klasse Nachricht, deren Inhalt 100 verschiedene Datentypen annehmen kann. Wie kann ich, ohne Reflection oder RTTI, möglichst elegant und entkoppelt ein Nachricht-Objekt aufgrund eines Strings erstellen und im Nachhinein den Inhalt der Nachricht im passenden Datentyp abfragen.



    Viele Grüße,
    Delta
    Delta 38 ist offline

  2. #2 Zitieren
    Tieftöner Avatar von Lookbehind
    Registriert seit
    Dec 2007
    Beiträge
    15.176
    Gegenfrage: Wie findest du denn heraus, welcher Nachrichten-Typ in deiner Nachricht drin ist? Gibts da irgendein Feld, wo du das auslesen kannst? Sonst wird die Zuordnung doch ohnehin schwierig.
    Lookbehind ist offline

  3. #3 Zitieren
    Ritter Avatar von Delta 38
    Registriert seit
    Nov 2008
    Ort
    Bremen
    Beiträge
    1.251
    Im Nachrichtenkopf gibt es ein Feld, dass den Nachrichtentyp eindeutig identifiziert. Ich mache es bisher so, dass ich für jede Nachricht die mich interessiert einen Decorator erstelle, in dessen Konstruktor ich die Nachricht übergebe. Dieser stellt dann eine Methode mit dem geeigneten Rückgabewert zur Verfügung. Die Umwandlung im Decorator läuft über einen austauschbaren Adapter. Da ich jedoch ganz sicher nicht der erste mit diesem Problem bin, dachte ich es gibt vielleicht eine super elegante Lösung, die mir bisher noch nicht über den Weg gelaufen ist.
    Wenn ich so darüber nachdenke, mutet mein Ansatz ja eher an das Fabrikmuster an. Ich übergebe den String-Identifier an eine Klasse und die gibt mir mein gewünschtes Objekt zurück
    Delta 38 ist offline Geändert von Delta 38 (16.05.2018 um 19:51 Uhr)

  4. #4 Zitieren
    Ritter Avatar von magges
    Registriert seit
    Jul 2004
    Beiträge
    1.224
    Mein erster Gedanke beim Lesen war auch das Factory Pattern, wenn es um die Erstellung der konkreten Nachrichtenobjekte geht.
    magges ist offline

  5. #5 Zitieren
    Legende Avatar von jabu
    Registriert seit
    Jul 2011
    Beiträge
    7.328
    Drei denkbare Ansätze, die mir auf die Schnelle eingefallen sind (von wer weiß wie vielen möglichen, ohne Anspruch auf die passendsten):
    1. Ganz passabel ginge das wohl schon per switch-case (das Typlabel als Enumerationskonstante auslegen), aber umständlich sähe das aus. Doch das kann auch täuschen, denn es wäre immerhin flexibel, lose gekoppelt und performanter als man vielleicht denkt.
      Das eigentlich Umständliche daran wäre die zusätzliche explizite Fallunterscheidung, obwohl der Typ längst klar ist. Praktisch wäre immerhin, dass der Downcast lediglich sicherheitshalber auf Erfolg geprüft werden müsste (Misserfolg wäre ein Bug und nicht Teil des Konzeptes). Wenn alles in Ordnung ist, könnte der Check in der Release-Version entfallen.
    2. Wenn man schon je Typ eine Enumerationskonstante hat, dann könnte man die natürlich als Arrayindizes für ein Array aus Objektreferenzen an Delegates nutzen. Jede Delegate-Methode wäre ein Handler für den typspezifischen Programmfluss. Sie kann ein Downcasting ohne Vorabselektion durchführen, weil der Typ bekannt ist. Man würde auch hier lediglich sicherheitshalber das Downcasting überprüfen.
      Mit diesem Trick hätte man eine Entkoppelung zwischen dem spezifischen Client-Code (bzw. Code des Aufrufers) und der zu behandelnden Klasse "Nachricht" erwirkt, was sich noch erweitern ließe, indem man gleich bei Bekanntwerden des Typs die passende Referenz auf die Delegate-Methode in "Nachricht" als Handler-Referenz speichert, sodass man beispielsweise eine asynchrone Nachrichtenabarbeitung mit allem Drumherum (wie bei einem Betriebssystem oder Framework) unterstützen kann, wobei weitere Typen durch den Client implementiert werden können (und normalerweise auch müssen), da das Framework die spezifischen Delegates nicht kennen muss (und normalerweise nicht kennt).
      Es wäre also in beiden vorgenannten Fällen, welche Delegates nutzen (per Array oder per Handler-Referenz innerhalb von "Nachricht"), nicht mehr nötig, an der Stelle des Aufrufs eine Fallentscheidung zu treffen, da sie vorab getroffen wurde. Das ähnelt jeweils vom Konzept her einem Funktionszeiger aus C oder C++.
    3. Es ginge natürlich auch eine Nummer einfacher, wenn man nicht maximale Flexibilität benötigt: Man füge jeder Kindklasse von "Nachricht" eine oder mehrere virtuelle Methoden hinzu, welche als Wrapper für den typspezifischen Programmfluss dienen. Durch die Polymorphie wird die Selektion elegant versteckt.

    Grundsätzlich wirft dein Code für mich noch zu viele Fragen auf, vor allem bezüglich Zuständigkeit, Ownership, Programmfluss und Persistenz, sodass ich nicht wirklich etwas empfehlen kann. Mehr als diese Gedankenanstöße sind also, zumindest vorerst, nicht drin gewesen. Aber ich glaube schon, dass man daraus ableiten kann, wo die Probleme liegen, spätestens dann, wenn man für sich auch diese offenen Fragen beantwortet hat.

    Ich weiß nicht, ob dir das hilft. Einen Versuch sollte es hoffentlich wert sein.
    jabu ist offline

  6. #6 Zitieren
    Pretty Pink Pony Princess  Avatar von Multithread
    Registriert seit
    Jun 2010
    Ort
    Crystal Empire
    Beiträge
    11.228
    Wenn du für alle deine 100 Fälle weisst, welchen Typ du haben musst, das ganze aber Dynamisch ist, würde Ich eine art Factory wählen.

    Am Anfang registrierst du für jeden Nachrichtentyp ein Delegate, welches dir das Objekt zurückgibt.
    Code:
    dictionary<string,delegate<object>>
    Dann kannst du dynamisch aus dem Dictionary das für dich richtige Objekt erstellen und über ein Interface oder einen Cast deine Werte abrufen (da wo du Bücher brauchst, müsste deine Klasse vom Interface IHasBooks abgeleitet sein)

    Als Resultat hast hast du dann einen 'Nachrichtenparser', welcher ein Property vom Typ Objekt hat. Und in diesem Objekt ist die Klasse (erstellt über das statische Dictionary mit den registrierten Typen). Was nicht im Dictionary ist, wirft eine 'UnknownMesssageTypeException'[Bild: 006.gif]

    Kannst du mal 2-3 Beispiele der Nachrichten posten.

    Von einem grossen Switch-Case würde Ich abraten. Insbesondere wenn später weitere Typen hinzukommen, führt das schnell zu Fehlern.
    Ich würde Jabu's 2te Variante nehmen, aber als oben genantes statisches Dictionary und nicht als Array.

    ggf. kann ich auch ein Beispiel machen. Ich habe nämlich keine Ahnung wie diese ganzen Design Pattern heissen, welche Ich täglich so anwende
    [Bild: AMD_Threadripper.png] Bei Hardware gibt es keine eigene Meinung, bei Hardware zählen nur die Fakten.


    Probleme mit der Haarpracht? Starres Haar ohne Glanz? TressFX schafft Abhilfe. Ja, TressFX verhilft auch Ihnen zu schönem und Geschmeidigen Haar.
    [Bild: i6tfHoa3ooSEraFH63.png]
    Multithread ist offline

  7. #7 Zitieren
    Ritter Avatar von Delta 38
    Registriert seit
    Nov 2008
    Ort
    Bremen
    Beiträge
    1.251
    Danke für eure Anregungen!
    Ich kann die Vorschläge leider erst am Mittwoch ausprobieren, die Implementierung der Factory über ein Dictonary statt switch-case finde ich sehr vielversprechend. Ich versuche dann auch nochmal ein vernünftiges Diagramm zu zeichnen.
    Delta 38 ist offline

  8. #8 Zitieren
    Ritter
    Registriert seit
    Feb 2003
    Beiträge
    1.554
    Ich glaube, du machst es dir ein bisschen zu kompliziert. Schreibe doch zuerst eine abstrakte Basisklasse mit generischen Inhalt:

    Code:
    public abstract class Nachricht<TContent>
    {
        public string Header {get; set; }
        public TContent Content { get; set; }
        public string Footer { get; set; }
    }
    Nun kannst du doch für jede Nachricht eine eigene Klasse schreiben:
    Code:
    public abstract class BuchNachricht : Nachricht<IList<Buch>>
    {
        public string Header {get; set; }
        public TContent Content { get; set; }
        public string Footer { get; set; }
    }
    Wenn du jetzt den Parser schreibst und du nun herausfinden willst, um welchen konkreten Datentyp es sich handelt, schreibst du eine Factory-Methode.
    Der Parser kann ja ruhig alles als ein Nachricht-Datentyp zurückliefern. Wenn du aber später die konkrete BuchNachricht benötigst, machst du einfach ein Downcast.
    Code:
    BuchNachricht buchNachricht = (BuchNachricht)nachricht;
    Whiz-zarD ist offline

  9. #9 Zitieren
    Ritter Avatar von Delta 38
    Registriert seit
    Nov 2008
    Ort
    Bremen
    Beiträge
    1.251
    Das Problem hat sich irgendwie in Luft aufgelöst
    Alle Nachrichten bestehen aus zunächst einmal aus Strings. Vorher wollte ich die Nachricht immer schon in der Empfangsmethode parsen, aber da eine Methode immer nur eine Sache tun soll, habe ich das Parsen dort wieder rausgenommen und den Rückgabetyp auf String geändert. Ich (als Programmierer) weiß vorher schon, welche Nachricht ich als Antwort bekomme (Request-Response Prinzip) und kann den empfangenen String ganz gemütlich in die korrekte Nachrichteninstanz schieben.

    Vielen Dank für eure Hilfe, die Idee ist mir nämlich erst gekommen, als ich versucht habe eure Lösungsvorschläge zu implementieren
    Delta 38 ist offline

  10. #10 Zitieren
    Legende Avatar von jabu
    Registriert seit
    Jul 2011
    Beiträge
    7.328
    Zitat Zitat von Delta 38 Beitrag anzeigen
    [...] Ich (als Programmierer) weiß vorher schon, welche Nachricht ich als Antwort bekomme (Request-Response Prinzip) [...]
    Deinen Ansätzen nach bin ich vom Gegenteil ausgegangen, also dass die Nachrichten nacheinander mit unbekanntem Typ hereintrudeln und dass dann vom Typ abhängige Aktionen auszuführen sind (nachdem konvertiert wurde), denn unter diesen Voraussetzungen würde sich der Knoten unter Umständen nicht so einfach auflösen, weswegen ich das mal angesichts weitgehend unbekannter Anforderungen als Arbeitshypothesse (und sportliches Rätselraten zugleich) angenommen hatte. Wenn die besagte Arbeitshypothese nicht wenigstens als eine mögliche Anforderung mitberücksichtigt werden muss, dann stellt sich das Problem natürlich nicht oder zumindest anders.

    Wie auch immer, es freut mich, dass du einen Weg gefunden hast und dass du dich zurückgemeldet hast.
    jabu ist offline

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •