对于多个方法(所有方法都接受不同的派生类作为参数),正确的 C# 设计模式是什么?

发布于 2024-09-14 13:14:21 字数 758 浏览 3 评论 0原文

我有一个基类:

class Message

和两个派生类:

class SimpleMessage : Message
class ComplexMesssage : Message

这些类型在代码的另一部分中使用,如下所示:

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

这些方法不在 Message 类内部,因为处理不是消息的一部分。

现在,我想避免使用 if/else/switch 结构,因为消息有很多类型。这里使用的最佳设计模式是什么?

一种选择是使用策略模式进行封装(至少按照我的理解):

class ProcessableMessage
{
delegate void ProcessMessageDelegate(Message m)

private Message m;
private ProcessMessageDelegate ProcessMessage;
}

但是让所有处理方法接受基本类型 Message 并在内部进行强制转换真的是最佳实践吗? 另一个问题是消息的动态类型(简单或复杂)实际上存储在此类中的两个位置 - 消息和处理算法,这看起来有点丑陋。

还有更好的解决方案吗?

谢谢!!
阿萨夫

I have a base class:

class Message

And two deriving classes:

class SimpleMessage : Message
class ComplexMesssage : Message

These types are used in another part of the code as such:

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

These methods are not inside the class Message, as the processing is not part of the message.

Now, i would like to avoid an if/else/switch structure, because there are many types of messages. What is the best design pattern to use here?

One option is to encapsulate using the strategy pattern (at least as i understand it):

class ProcessableMessage
{
delegate void ProcessMessageDelegate(Message m)

private Message m;
private ProcessMessageDelegate ProcessMessage;
}

But is it really best practice to make all the processing methods accept the base type Message, and cast inside?
And another problem would be with the fact that the dynamic type of the message (simple or complex) is actually stored in 2 places in this class - the message and the process algorithm, which seems kind of ugly.

Any better solutions out there?

Thanks!!
Assaf

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

放赐 2024-09-21 13:14:21

我会在这里使用访客模式:

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);
    }
}

I'd use the visitor pattern here:

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);
    }
}
最偏执的依靠 2024-09-21 13:14:21

为什么不直接添加虚拟方法:

class Message
{
    public abstract void Process();
}

如果您确实需要将代码分开:

class Message
{
    public abstract void Process();
}

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

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

我的意思是,我们这里需要什么样的灵活性?还涉及哪些其他类别?您周围的场景是什么?在没有任何其他上下文的情况下,这确实是最容易理解、也最容易实现的方法。有时,人们会在确实不需要的时候添加一些设计缺陷。

策略模式通常适用于如果您希望使用不同的方法来处理相同的消息类型,并希望在运行时切换它们。如果一种类型的处理通常与一种类型的消息一起使用,那么您不需要使其变得更加复杂。

Why not just add the virtual method:

class Message
{
    public abstract void Process();
}

If you really need to keep the code separated:

class Message
{
    public abstract void Process();
}

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

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

I mean, what sort of flexibility do we need here? What other classes are involved? What is your surrounding scenario? Without any other context, this is really the simplest method to understand, and easiest to implement. Sometimes people add design cruft when it really isn't needed.

The strategy pattern is generally for if you wanted to have different methods to process the same message type, and wanted to switch them at runtime. If one type of processing generally goes with one type of message, then you don't need to make it any more complicated.

蓝礼 2024-09-21 13:14:21

我喜欢上面的访客方法。不过,只是为了好玩,我展示了如何在 VS2008 和 VS2010 中使用 T4 减少代码冗余。

冗余来自于每条消息都需要一个 Visit 方法。此外,每个方法都需要一个简单但冗余的 Accept 实现。更接近“不要重复自己”的一种方法是使用 T4 生成代码。

为了测试以下示例,请在 VS 中添加一个类,但将扩展名从 .cs 更改为 .tt。您现在将获得两个文件:.tt 文件和连接到 .tt 文件的 .cs 文件。

.tt 文件是生成.cs 文件的模板。当时它们是相同的。

使用它作为 .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);
      }
   }
<#
   }
#>
}

这应该生成一个如下所示的 CS 文件:

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);
      }
   }
}

为什么这不那么冗余?因为现在每当我想添加新消息时,我只需将其添加到模板中:

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

需要注意的是,所有消息都是部分生成的,因为我们需要不同的消息负载。这是在另一个文件中指定的,它可能如下所示:

   partial class SimpleMessage
   {
      public string Name;
   }

   partial class ComplexMessage
   {
      public XmlDocument Xml;
   }

对于那些喜欢 T4 声音的人,请查看此博客:http://www.olegsych.com/2008/09/t4-tutorial-creatating-your-first-code-generator/

I like the Visitor approach above. However just for the fun of it I show a bit on how to reduce redundancy in code using T4 in VS2008 and VS2010.

The redundancy comes from that for each message you need a Visit method. Also each method needs a simple but redundant implementation of Accept. One way to get closer to "Do not repeat yourself" is generating the code using T4.

In order to test the following sample add a class in VS but change the extension from .cs to .tt. You will now get two files a .tt file and a .cs file connected to the .tt file.

The .tt file is a template that generates .cs file. At the time they are identical.

Use this as the content for the .tt file:

<#@ 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);
      }
   }
<#
   }
#>
}

This should generate a CS file that looks like this:

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);
      }
   }
}

Why is this less redudant? Because now whenever I like to add a new meessage I just add it to the template:

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

It's important to note that all messages are generated as partial because we need different payloads for a message. This is specified in another file and it could look like this:

   partial class SimpleMessage
   {
      public string Name;
   }

   partial class ComplexMessage
   {
      public XmlDocument Xml;
   }

For those that likes the sound of T4 check this blog: http://www.olegsych.com/2008/09/t4-tutorial-creatating-your-first-code-generator/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文