Как можно избежать оператора switch, если вызываемый метод не возвращает тип возврата? - PullRequest
5 голосов
/ 23 мая 2011

Я читал книгу под названием Чистый код Справочник по гибкому программному обеспечению . Автор в книге мотивирует, что оператора switch следует избегать, а если его нельзя избежать, его следует отнести к фабричным методам. У меня есть объект подключения, который получает различные PDU (протокольные блоки данных). PDU могут быть разными и могут приниматься в любом порядке. Так что, если у меня есть метод, например:

public BasePdu ReceiveNext();

потому что я не могу сказать, какой тип пакета, пока он не будет полностью получен. В заголовке PDU есть идентификатор того, какого типа он должен быть. Это означает, что вызывающий метод должен выяснить, каков тип PDU, и на основе этого вызвать соответствующий метод для его обработки. Это звучит как идеальный пример для заявления переключателя. Объект, который содержит соединение, в идеале хотел бы иметь два потока. Один для приема PDU, а другой для обслуживания очереди PDU, подлежащих отправке.

Теперь я знаю, что вы не можете следовать всем хорошим советам и что есть только некоторые обстоятельства, которые являются исключением из правила. Это один из них? Или есть способ обойти это, о котором я просто еще не подумал.

UPDATE:

Я слышу, что многие люди говорят, создавая подклассы обработчиков ответов. Проблема заключается в том, что содержащий объект имеет много контекста и дополнительной информации, которая понадобится обработчикам, например, поиск, регулирование и т. Д. И т. Д. Внедрить всю эту информацию в подклассы обработчиков было бы довольно сложной задачей, а также разделить много логики, когда лучше чувствовать себя инкапсулированным в объекте, в котором он сейчас находится.

Ответы [ 5 ]

4 голосов
/ 23 мая 2011

Просто создайте 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 }
 }

затем зарегистрировать обработчики в приемнике для каждого PDUType и позволить обработчику делать все, что он хочет сdata.

Трудно дать более конкретный совет, не зная, что именно вы хотите делать с полученными пакетами.

4 голосов
/ 23 мая 2011

Лично я бы не слишком беспокоился об этом; если это выглядит как хорошее место для оператора switch, используйте его. С другой стороны, это также выглядит как ситуация, когда вы можете использовать фабричный метод, если каждый тип PDU обрабатывается классом, а не методом. И, согласно вашей книге, вам разрешено использовать операторы switch тогда

1 голос
/ 23 мая 2011

Если я правильно понимаю ваш вопрос, у вас действительно два вопроса:


Как создать правильный PDU, когда вы получили имя, без использования switch.

Создание простой фабрики с помощью словаря Dictionary<string, Func<PduBase>>


Как метод, вызывающий public BasePdu ReceiveNext();, может обрабатывать ее правильно без использования switch

Не используйте RecieveNext метод.Создайте метод AddPduHandler<T>(IPduHandler<T> handler) where T : PduBase для класса, получающего все PDU.Сохраните все обработчики в словаре с типом в качестве ключа: Dictionary<Type, Delegate>

Сохранение делегата является своего рода хитростью, поскольку вы не можете работать с типизированным интерфейсом в принимающем классе.

Обновление

Это решение не нарушает принцип подстановки Лискова, который делают все реализации, использующие switch.Это означает, что этот класс будет работать независимо от того, сколько у вас различных типов 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");
    }
1 голос
/ 23 мая 2011

Причина, по которой следует избегать простых выражений, заключается не в том, что если структуры лучше (если используется переключатель, куча «если» сделает его хуже, а не лучше), то в основном потому, что проблема не решается ОО-способом.

С точки зрения ОО почти всегда лучше использовать полиморфизм, чем оператор switch.

В вашем примере, вероятно, лучше использовать фабричный метод, чтобы обеспечить соответствующий обработчик для вашего типа пакета.

0 голосов
/ 23 мая 2011

не уверен, что это именно тот вопрос, но наличие разных экземпляров, обработка которых отличается в зависимости от идентификатора, фактически является случаем для создания подклассов (выбор подкласса, представляющего информацию, которая ранее была сохранена в идентификаторе), например BasePdu и попросите компилятор выяснить, какой метод использовать. если вы делаете это путем переключения, это означает, что вы не в полной мере используете преимущества структурирования кода с помощью подклассов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...