如果调用方法不知道返回类型,如何避免使用switch语句?

问题描述

| 我一直在阅读一本名为《清洁代码:敏捷软件技巧手册》的书。该书的作者鼓励应避免使用switch语句,如果不能避免,则应使用工厂方法我有一个连接对象,正在接收各种PDU(协议数据单元)。 PDU有所不同,可以以任何顺序接收它们。因此,如果我有一个方法例如:
public BasePdu ReceiveNext();
因为在完全接收到数据包之前,我无法确定它是什么。 PDU的标头中有一个标识符,说明它应该是哪种类型。这意味着调用方法将要弄清PDU的类型是什么,并基于该调用来处理相关的方法。这听起来像是switch语句的完美示例。理想情况下,包含连接的对象希望有两个线程。一个用于接收PDU,另一个用于服务要发送的PDU队列。 现在我知道您不能遵循所有好的建议,并且在某些情况下是该规则的例外。这是其中之一吗?还是有一种我还没有想到的解决方法。 更新: 我听到了很多人通过创建响应处理程序的子类说的话。问题在于,包含对象具有许多上下文以及处理程序将需要的其他信息,例如查找和限制等。将所有这些信息注入到处理程序的子类中将是一件很麻烦的事情,并且还会拆分当感觉更好地封装在当前对象中时,需要进行大量的逻辑处理。     

解决方法

就我个人而言,我不会对此太担心。如果看起来是进行switch语句的好地方,请使用一个。另一方面,如果每种PDU类型是由类而不是方法处理的,则这似乎也可以使用工厂方法。而且,按照您的书,您可以使用switch语句     ,只需创建一个PDUParserFactory即可,它使用PDU类型标识符上的switch语句根据PDU类型创建解析器。这是书说没问题的情况:) 更新:一种可能的方法
 class BasePDU
 {
     string Name { get; set; }
     ...
 }

 class PDUType1 : BasePDU
 {
     ...
 }
 ...

 class PDUReceiver
 {
     public event EventHandler<PDUReceivedEventArgs> PDUReceived;

     private void ParsePDU(byte[] data)
     {
          BasePDU pdu;
          switch (byte[0]) // PDU type selector
          {
              ....    parse PDU based on type
          }

          OnPDUReceived(pdu);
     }

      private void OnPDUReceived(BasePDU pdu)
      {
           var handler = PDUReceived;
           if (handler != null)
           {
                handler(this,new PDUReceivedEventArgs(pdu));
           }
      }
 }
然后,您可以将侦听器附加到事件:
 pduReceiver.PDUReceived += BaseHandler;
 pduReceiver.PDUReceived += PDUType1Handler;
 ...

 void PDUType1Handler(object sender,PDUReceivedEventArgs e)
 {
      // only care about PDUType1
      if (e.PDU.GetType() != typeof(PDUType1))
            return;
      ....
 }
或者,您也可以在接收方中创建事件处理程序字典,将pdu类型映射到事件处理程序,然后让处理程序仅注册特定类型。这样,并非所有处理程序都会为每个接收到的PDU调用。 除了保留PDU类型层次结构之外,您还可以拥有:
 class PDU
 {
      public PDUType PDUType { get; }
      public byte[] PDUData { get }
 }
然后在接收器中为每个“ 4”注册处理程序,并让处理程序对数据进行任何所需的处理。 如果不知道您到底想对收到的数据包做什么,很难给出更具体的建议。     ,如果我正确理解您的问题,您实际上有两个问题: 在不使用ѭ5的情况下收到名称后如何创建正确的PDU。 使用字典
Dictionary<string,Func<PduBase>>
创建一个简单的工厂 调用
public BasePdu ReceiveNext();
的方法如何在不使用
switch
的情况下正确处理 不要使用
RecieveNext
方法。为接收所有PDU的类创建一个“ 10”方法。将所有处理程序存储在类型为键的字典中:
Dictionary<Type,Delegate>
存储委托是一种技巧,因为您不能在接收类中使用类型化的接口。 更新资料 此解决方案不会违反使用“ 5”的所有实现所能做到的Liskovs替代原理。这意味着无论您拥有多少种不同类型的PDU,此类都将起作用。 由于每个处理程序都与其他事物隔离开来,因此测试您的应用程序也更加容易。 值得一提的是,所有内容都是类型化的(读者类除外),这将使查找错误变得更容易,而无需使用魔术或类似方法。
    public class Receiver
    {
        Dictionary<Type,MethodMapping> _handlers = new Dictionary<Type,MethodMapping>();
        Dictionary<string,Func<PduBase>> _factories = new Dictionary<string,Func<PduBase>>();

        // Small container making it easier to invoke each handler
        // also needed since different generic types cannot be stored in the same 
        // dictionary
        private class MethodMapping
        {
            public object Instance { get; set; }
            public MethodInfo Method { get; set; }
            public void Invoke(PduBase pdu)
            {
                Method.Invoke(Instance,new[] {pdu});
            }
        }

        // add a method used to create a certain PDU type
        public void AddFactory(string name,Func<PduBase> factoryMethod)
        {
            _factories.Add(name,factoryMethod);
        }

        // register a class that handles a specific PDU type
        // we need to juggle a bit with reflection to be able to invoke it
        // hence everything is type safe outside this class,but not internally.
        // but that should be a sacrifice we can live with.
        public void Register<T>(IPduHandler<T> handler) where T : PduBase
        {
            var method = handler.GetType().GetMethod(\"Handle\",new Type[] { typeof(T) });
            _handlers.Add(typeof(T),new MethodMapping{Instance = handler,Method = method});
        }

        // fake that we\'ve received a new PDU
        public void FakeReceive(string pduName)
        {
           // create the PDU using the factory method
            var pdu = _factories[pduName]();

            // and invoke the handler.
            _handlers[pdu.GetType()].Invoke(pdu);
        }
    }

    public interface IPduHandler<in T> where T: PduBase
    {
        void Handle(T pdu);
    }

    public class TempPdu : PduBase
    {}

    public class TempPduHandler : IPduHandler<TempPdu>
    {
        public void Handle(TempPdu pdu)
        {
            Console.WriteLine(\"Handling pdu\");
        }
    }

    public class PduBase
    { }



    private static void Main(string[] args)
    {
        Receiver r = new Receiver();
        r.AddFactory(\"temp\",() => new TempPdu());
        r.Register(new TempPduHandler());

        // we\'ve recieved a PDU called \"temp\". 
        r.FakeReceive(\"temp\");
    }
    ,避免使用swith语句的原因并不是因为结构更好(使用开关时,如果使用一堆if会使情况变得更糟,而不是更好),主要原因是无法以OO方式解决问题。 从面向对象的角度来看,使用多态性几乎总是比使用switch语句更好。 在您的示例中,最好使用工厂方法为您的包类型提供适当的处理程序。     ,不知道这是否是关键点,但是实际上要创建一个实例,例如创建BasePdu和让编译器找出使用哪种方法。如果通过切换来执行此操作,则意味着您没有充分利用子类化来构造代码的优势。