2010-08-15 27 views
1

J'ai une classe de base:Quel est le modèle de conception C# correct pour plusieurs méthodes, toutes les acceptant comme des paramètres différents des classes dérivées?

class Message 

Et deux classes dérivées:

class SimpleMessage : Message 
class ComplexMesssage : Message 

Ces types sont utilisés dans une autre partie du code en tant que tel:

void ProcessSimpleMessage(SimpleMessage m) 
void ProcessComplexMessage(ComplexMessage m) 

Ces méthodes sont pas à l'intérieur de la classe Message, car le traitement ne fait pas partie du message.

Maintenant, je voudrais éviter une structure if/else/switch, car il y a beaucoup de types de messages. Quel est le meilleur modèle de conception à utiliser ici?

Une option consiste à encapsuler en utilisant le modèle de stratégie (au moins si je comprends bien):

class ProcessableMessage 
{ 
delegate void ProcessMessageDelegate(Message m) 

private Message m; 
private ProcessMessageDelegate ProcessMessage; 
} 

Mais est-ce la meilleure pratique vraiment faire toutes les méthodes de traitement acceptent le type de base du message et jeté à l'intérieur ? Et un autre problème serait avec le fait que le type dynamique du message (simple ou complexe) est réellement stocké dans 2 endroits dans cette classe - le message et l'algorithme de processus, qui semble plutôt moche.

De meilleures solutions là-bas?

Merci!
Assaf

+1

Je sais que vous avez mentionné «Ces méthodes ne sont pas à l'intérieur du message de classe, car le traitement ne fait pas partie du message. "Mais y a-t-il une raison spécifique à cela? Considérant que vous avez une logique de traitement spécifique à chaque type de message, ne serait-il pas plus facile d'avoir une méthode de processus virtuelle dans le message de classe de base que chaque sous-classe peut surcharger? Message.Process() - dont l'objet est transmis? – InSane

+0

référez Merlyn Morgan-Graham réponse ci-dessous. +1 pour cela – InSane

+0

Qu'est-ce que vous essayez d'atteindre? Discuter du problème pourrait vous donner de meilleures réponses ... au lieu d'essayer de faire correspondre une solution. – Gishu

Répondre

3

Pourquoi ne pas simplement ajouter la méthode virtuelle:

class Message 
{ 
    public abstract void Process(); 
} 

Si vous avez vraiment besoin de garder le code séparé:

class Message 
{ 
    public abstract void Process(); 
} 

class SimpleMessage 
{ 
    public override void Process() 
    { 
     new SimpleMessageProcessor().Process(); 
    } 
} 

class SimpleMessageProcessor 
{ 
    internal void Process() 
    { 
     // ... 
    } 
} 

Je veux dire, quel genre de flexibilité avons-nous besoin ici ? Quelles autres classes sont impliquées? Quel est votre scénario environnant? Sans aucun autre contexte, c'est vraiment la méthode la plus simple à comprendre et la plus simple à mettre en œuvre. Parfois, les gens ajoutent du design cru quand ce n'est pas vraiment nécessaire.

Le modèle de stratégie est généralement utilisé si vous souhaitez disposer de différentes méthodes pour traiter le même type de message et que vous souhaitez les activer au moment de l'exécution. Si un type de traitement est généralement associé à un type de message, il n'est pas nécessaire de le rendre plus compliqué.

+0

Pour des raisons de conception, les méthodes de traitement ne peuvent pas être incluses dans la classe Message. Cette solution est possible à l'aide d'un wrapper, mais, Les signatures des méthodes de traitement sont différentes - Cela force la première ligne dans chaque méthode de processus à être une distribution: SimpleMessage sm = (SimpleMessage) m_Message; –

+0

Ce que je veux éviter ici est le code qui ressemble à ceci: si (typeof (message) == SimpleMessage) Message ProcessSimpleMessage ((SimpleMessage)) if (typeof (message) == ComplexMessage) ProcessComplexMessage ((ComplexMessage) message)) ... .. –

+0

Il n'est pas rare que les messages aient différentes manières de les traiter. Pensez par exemple au message Windows pour un clic de souris. Le comportement diffère en fonction de qui l'écoute. – FuleSnabel

3

j'utiliser le modèle de visiteur ici:

public interface IMessageVisitor 
{ 
    void VisitSimple(SimpleMessage msg); 
    void VisitComplex(ComplexMessage msg); 
} 

public abstract class Message 
{ 
    public abstract void Accept(IMessageVisitor visitor); 
} 

public class SimpleMessage : Message 
{ 
    public override void Accept(IMessageVisitor visitor) 
    { 
     visitor.VisitSimple(this); 
    } 
} 

public class ComplexMessage : Message 
{ 
    public override void Accept(IMessageVisitor visitor) 
    { 
     visitor.VisitComplex(this); 
    } 
} 

public class MessageProcessor : IMessageVisitor 
{ 
    void IMessageVisitor.VisitSimple(SimpleMessage msg) 
    { process simple message } 

    void IMessageVisitor.VisitComplex(ComplexMessage msg) 
    { process complex message } 

    public void Process(Message msg) 
    { 
     msg.Accept(this); 
    } 
} 
+0

Où Implémentez-vous la méthode abstraite Accepter? – mustafabar

+0

Sur SimpleMessage et ComplexMessage. C'est une méthode d'une ligne, qui appelle 'visitor.VisitSimple (this)' ou 'visitor.VisitComplex (this)'. –

0

J'aime l'approche des visiteurs ci-dessus. Cependant, juste pour le plaisir, je montre un peu comment réduire la redondance dans le code en utilisant T4 dans VS2008 et VS2010. La redondance vient de cela pour chaque message, vous avez besoin d'une méthode de visite. Chaque méthode nécessite également une implémentation simple mais redondante de Accept. Une façon de se rapprocher de "Ne vous répétez pas" est de générer le code en utilisant T4.

Afin de tester l'exemple suivant, ajoutez une classe dans VS mais changez l'extension de .cs en .tt. Vous obtiendrez maintenant deux fichiers un fichier .tt et un fichier .cs connecté au fichier .tt.

Le fichier .tt est un modèle qui génère un fichier .cs. Au moment où ils sont identiques.

Utilisez ce que le contenu du fichier .tt:

<#@ template language="C#" #> 
<# 
    // On VS2008 change C# above to C#v3.5 

    // ----------------------------------------------------- 
    // Here we declare our different message types 
    var messageTypes = new [] 
     { 
     "Simple", 
     "Complex", 
     "Other", 
     }; 
    // ----------------------------------------------------- 
#> 

namespace MessageProcessor 
{ 
    partial interface IMessageVisitor 
    { 
<# 
    // Let's generate all message visitor methods 
    foreach (var messageType in messageTypes) 
    { 
#> 
     void Visit (<#=messageType#>Message message); 
<# 
    } 
#> 
    } 
    abstract partial class Message 
    {  
     public abstract void Accept (IMessageVisitor visitor); 
    } 
<# 
    // Let's generate all message types 
    foreach (var messageType in messageTypes) 
    { 
#> 
    sealed partial class <#=messageType#>Message : Message 
    {  
     public override void Accept (IMessageVisitor visitor) 
     { 
     visitor.Visit (this); 
     } 
    } 
<# 
    } 
#> 
} 

Cela devrait générer un fichier CS qui ressemble à ceci:

namespace MessageProcessor 
{ 
    partial interface IMessageVisitor 
    { 
     void Visit (SimpleMessage message); 
     void Visit (ComplexMessage message); 
     void Visit (OtherMessage message); 
    } 
    abstract partial class Message 
    {  
     public abstract void Accept (IMessageVisitor visitor); 
    } 
    sealed partial class SimpleMessage : Message 
    {  
     public override void Accept (IMessageVisitor visitor) 
     { 
     visitor.Visit (this); 
     } 
    } 
    sealed partial class ComplexMessage : Message 
    {  
     public override void Accept (IMessageVisitor visitor) 
     { 
     visitor.Visit (this); 
     } 
    } 
    sealed partial class OtherMessage : Message 
    {  
     public override void Accept (IMessageVisitor visitor) 
     { 
     visitor.Visit (this); 
     } 
    } 
} 

Pourquoi est-ce moins redudant? Parce que maintenant chaque fois que je veux ajouter un nouveau meessage Je viens d'ajouter au modèle:

var messageTypes = new [] 
     { 
     "Simple", 
     "Complex", 
     "Other", 
     "YetAnotherOne", 
     }; 

Il est important de noter que tous les messages sont générés en partie parce que nous avons besoin de différentes charges utiles pour un message. Ceci est spécifié dans un autre fichier et il pourrait ressembler à ceci:

partial class SimpleMessage 
    { 
     public string Name; 
    } 

    partial class ComplexMessage 
    { 
     public XmlDocument Xml; 
    } 

Pour ceux qui aime le son de contrôle T4 ce blog: http://www.olegsych.com/2008/09/t4-tutorial-creatating-your-first-code-generator/

+0

J'ai remarqué que vous dites que vous ne pouvez pas modifier les classes Message. Eh bien, vous pouvez toujours généraliser la classe des visiteurs en utilisant T4. – FuleSnabel

+0

Merci! Je n'avais aucune idée que cela existait. –