4 Stimmen

Entwurfsmuster für das Laden mehrerer Nachrichtentypen

Bei der Durchsicht von SO stieß ich auf eine Frage zu Handhabung mehrerer Nachrichtentypen . Mein Anliegen ist - wie lade ich eine solche Nachricht auf eine saubere Art und Weise? Ich beschloss, eine separate Klasse mit einer Methode zu haben, die eine Nachricht jedes Mal lädt, wenn sie aufgerufen wird. Diese Methode sollte eine neue Instanz eines konkreten Nachrichtentyps (z.B. AlphaMessage, BetaMessage, GammaMessage, etc.) erzeugen und diese als Nachricht zurückgeben.

class MessageLoader
{
    public Message Load()
    {
        // ...
    }
}

Der Code innerhalb der Methode ist etwas, das wirklich schrecklich für mich aussieht, und ich würde sehr gerne refactor it/get rid of it:

Message msg = Message.Load(...); // load yourself from whatever source
if (msg.Type == MessageType.Alpha) return new AlphaMessage(msg);
if (msg.Type == MessageType.Beta) return new BetaMessage(msg);
// ...

Wenn das ganze Design zu chaotisch aussieht und ihr eine bessere Lösung habt, bin ich bereit, die ganze Sache umzustrukturieren.

Wenn meine Beschreibung zu chaotisch ist, lassen Sie mich bitte wissen, was darin fehlt, und ich werde die Frage überarbeiten. Ich danke Ihnen allen.

Editar: Was mir an diesem Code nicht gefällt, ist, dass ich eine Instanz einer Nachricht erstellen muss (weil sie weiß, wie sie sich selbst lädt) und sie dann mit einem konkreten Nachrichtentyp dekorieren muss (weil Dekoratoren wissen, wie sie die Eigenschaft Data von msg interpretieren können). Vielleicht wird die Frage dadurch etwas klarer.

3voto

P.Brian.Mackey Punkte 41258

Ich stimme CkH insofern zu, als das Fabrikmuster das Problem lösen wird. Ich habe ein dummes Beispiel als Beweis für das Konzept geschrieben. Es soll nicht zeigen, dass eine gute Klasse entworfen wurde, sondern nur, dass ein einfaches Factory-Muster funktioniert. Selbst wenn Sie mehrere Nachrichtentypen und Handler verwenden, sollten Sie dieses Muster nur geringfügig ändern müssen.

class Class12
{
    public static void Main()
    {
        Message m = new Message(1, "Hello world");
        IMessageHandler msgHandler = Factory.GetMessageHandler(m.MessageType);
        msgHandler.HandleMessage(m);

        Message m2 = new Message(2, "Adios world");
        IMessageHandler msgHandler2 = Factory.GetMessageHandler(m2.MessageType);
        msgHandler2.HandleMessage(m2);
    }
}
public class Factory
{
    public static IMessageHandler GetMessageHandler(int msgType)
    {
        IMessageHandler msgHandler = null;
        switch(msgType)
        {
            case 1: 
                msgHandler = new MessageHandler1();
                break;
            case 2: 
                msgHandler = new MessageHandler2();
                break;
            default: 
                msgHandler = new MessageHandler1();
                break;
        }
        return msgHandler;
    }
}
public class Message
{
    public int MessageType { get; set; }
    public string AMessage { get; set; }

    public Message(int messageType, string message)
    {
        this.MessageType = messageType;
        this.AMessage = message;
    }
}
public interface IMessageHandler
{
    void HandleMessage(Message m);
}
class MessageHandler1 : IMessageHandler
{

    #region IMessageHandler Members

    public void HandleMessage(Message m)
    {
        string message = m.AMessage;
        Console.WriteLine(message);
    }

    #endregion
}
class MessageHandler2 : IMessageHandler
{

    #region IMessageHandler Members

    public void HandleMessage(Message m)
    {
        string message = m.AMessage;
        Console.WriteLine("Hey there " + message);
    }

    #endregion
}

1voto

Dan Bryant Punkte 27022

Die nächste Abstraktionsebene besteht darin, die Ermittlung und Instanziierung von Nachrichten dynamisch zu gestalten. Dies wird häufig dadurch erreicht, dass jeder Nachricht ein Stringname zugeordnet wird oder der Name der Klasse als Bezeichner verwendet wird. Sie können Reflection verwenden, um verfügbare Nachrichtentypen zu ermitteln, sie in einem Dictionary zu speichern und die Instanziierung nach Namen zu ermöglichen. Dies kann weiter ausgebaut werden, um Nachrichten aus dynamisch geladenen "Plugin"-Assemblies einzubringen, mit entsprechenden Metadaten und Schnittstellen, um eine lose gekoppelte Komposition zwischen verschiedenen Nachrichten und Nachrichtenkonsumenten zu ermöglichen. Sobald Sie diese Ebene erreicht haben, empfehle ich Ihnen, sich mit Frameworks wie MEF die den Prozess der Erkennung und Instanzinjektion automatisieren.

Für Ihre einfache Anwendung ist Ihr Ansatz meiner Meinung nach bereits recht sauber. Eine Reihe von if-Anweisungen oder ein Schalter funktioniert sehr gut und ist leicht zu verstehen/zu pflegen, solange Sie eine relativ kleine und stabile Menge von Fällen haben.


Zusammenfassung der weiteren Diskussion in den Kommentaren:

Das Hauptproblem bei der Gestaltung war die Tatsache, dass die verschiedenen spezifischen Nachrichten von Message geerbt haben und dennoch eine Basis-Message instanziiert werden musste, bevor die spezifischeren Nachrichten weitere Analysen durchführen konnten. Dadurch wurde unklar, ob die Nachricht Rohinformationen enthalten oder als Basistyp für interpretierte Nachrichten dienen sollte. Besser wäre es, die RawMessage-Funktionalität in eine eigene Klasse aufzuteilen, um eine klare Trennung zu erreichen und das Gefühl der "doppelten Instanziierung" zu beseitigen.

Was das Refactoring mit DTOs und einer Mapperklasse betrifft:

Ich bevorzuge eigentlich Ihren Ansatz für eine app-spezifische Nachrichtenkodierung/-dekodierung. Wenn ich herausfinden will, warum FactoryTakenOverByRobotsMessage ungültige Daten enthält, erscheint es mir sinnvoll, dass die Parser-Methode für die Nachricht in den dekodierten Daten für die Nachricht enthalten ist. Schwierig wird es, wenn man verschiedene Kodierungen unterstützen möchte, da man dann die DTO deklarativ (z. B. mit Attributen) angeben und den verschiedenen Transportschichten die Entscheidung über die Serialisierung/Deserialisierung überlassen möchte. In den meisten Fällen, in denen ich Ihr Muster verwende, handelt es sich jedoch um einen sehr app-spezifischen Fall mit oft etwas inkonsistenten Nachrichtenformaten und verschiedenen proprietären Kodierungen, die sich nicht automatisch zuordnen lassen. Ich kann immer noch die deklarative Kodierung parallel zur proprietären, klasseninternen Kodierung verwenden und Dinge wie die Serialisierung meiner Nachrichten in XML zu Debugging-Zwecken tun.

0voto

John Punkte 15906

Bei C# brauchen Sie wahrscheinlich so etwas wie das, was Sie geschrieben haben, weil C# eine stark typisierte Sprache ist. Im Grunde müssen Sie die konkreten Klassen irgendwo in Ihrem Code erhalten.

0voto

Steven Evers Punkte 15926

Was Sie haben, sieht gut aus. Es ist unmissverständlich. Wenn Ihre AlphaMessage und BetaMessage Objekte sind Kinder von Message dann statt der Erstellung eines neuen Objekts, geben Sie einfach das Objekt gecastet.

if (msg.Type == MessageType.Alpha) return msg as AlphaMessage;
else if (msg.Type == MessageType.Beta) return msg as BetaMessage;

Der Verbrauchercode muss den Fall behandeln, dass der Cast fehlschlägt ( als gibt null zurück).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X