Логика - теперь Полиморфизм вместо Выключателя, но как насчет построения? - PullRequest
2 голосов
/ 11 марта 2009

Этот вопрос конкретно касается C #, но мне также интересны ответы на C ++ и Java (или даже на другие языки, если у них есть что-то классное).

Я заменяю операторы switch полиморфизмом в коде "C используя синтаксис C #", который я унаследовал. Я ломал голову над лучшим способом создания этих объектов. У меня есть два запасных метода, которые я использую. Я хотел бы знать, есть ли другие, жизнеспособные альтернативы, которые я должен рассмотреть, или просто проверка здравомыслия, что я на самом деле иду об этом разумным способом.

Методы, которые я обычно использую:

  1. Используйте всезнающий метод / класс. Этот класс будет либо заполнять структуру данных (скорее всего, Map), либо создавать на лету с помощью оператора switch.
  2. Используйте слепой и тупой класс, который использует конфигурационный файл и отражение для создания карты экземпляров / делегатов / фабрик / и т. Д. Затем используйте карту способом, аналогичным описанному выше.
  3. ???

Есть ли # 3, # 4 ... и т. Д., Которые я должен решительно рассмотреть?


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

Предыдущий псевдокод:

public string[] HandleMessage(object input) {

   object parser = null;
   string command = null;
   if(input is XmlMessage) {
      parser = new XmlMessageParser();
      ((XmlMessageParser)parser).setInput(input);
      command = ((XmlMessageParser)parser).getCommand();
   } else if(input is NameValuePairMessage) {
      parser = new NameValuePairMessageParser();
      ((NameValuePairMessageParser)parser).setInput(input);
      command = ((XmlMessageParser)parser).getCommand();
   } else if(...) {
      //blah blah blah
   }

   string[] result = new string[3];
   switch(command) {
      case "Add":
          result = Utility.AddData(parser);
          break;
      case "Modify":
          result = Utility.ModifyData(parser);
          break;
      case ... //blah blah
          break;
   }
   return result;
}

То, что я планирую заменить на (после большого рефакторинга других объектов), выглядит примерно так:

public ResultStruct HandleMessage(IParserInput input) {
   IParser parser = this.GetParser(input.Type);       //either Type or a property
   Map<string,string> parameters = parser.Parse(input);
   ICommand command = this.GetCommand(parameters);  //in future, may need multiple params 
   return command.Execute(parameters);              //to figure out which object to return.
}

Вопрос в том, какой должна быть реализация GetParser и GetCommand?

Размещение оператора switch (или вызова фабрики, состоящей из операторов switch) не похоже на то, что действительно решает проблему. Я просто перемещаю переключатель в другое место ... что может быть хорошо, так как его больше нет в середине моей основной логики.

Ответы [ 4 ]

2 голосов
/ 11 марта 2009

Возможно, вы захотите поместить свои экземпляры синтаксического анализатора на сами объекты, например,

public interface IParserInput
{
    ...
    IParser GetParser()
    ICommand GetCommand()
}

Любые параметры, которые нужны GetParser, должны теоретически предоставляться вашим объектом.

Что произойдет, так это то, что сам объект вернет их, а то, что происходит с вашим кодом:

public ResultStruct HandleMessage(IParserInput input) 
{
    IParser parser = input.GetParser();
    Map<string,string> parameters = parser.Parse(input);
    ICommand command = input.GetCommand();
    return command.Execute(parameters);
}

Теперь это решение не идеально. Если у вас нет доступа к IParserInput объектам, это может не сработать. Но, по крайней мере, ответственность за предоставление информации о надлежащем обработчике теперь ложится на анализатора, а не на обработчик, что представляется более правильным на данный момент.

1 голос
/ 11 марта 2009

Вы можете иметь

public interface IParser<SomeType> : IParser{}

И настроить структурную карту для поиска парсера для «SomeType»

Кажется, что команды связаны с синтаксическим анализатором в существующем коде, если вы находите его чистым для вашего сценария, вы можете оставить все как есть и просто запросить команду у анализатора.

Обновление 1: Я перечитал оригинальный код. Я думаю, что для вашего сценария, вероятно, будет наименьшее изменение для определения IParser, как указано выше, который имеет соответствующие GetCommand и SetInput.

Часть команды / ввода будет выглядеть примерно так:

    public string[] HandleMessage<MessageType>(MessageType input) {
       var parser = StructureMap.GetInstance<IParser<MessageType>>();
       parser.SetInput(input);
       var command = parser.GetCommand();
       //do something about the rest
   }

Ps. на самом деле, ваша реализация заставляет меня чувствовать, что старый код, даже без переключателей if и if, имел проблемы. Можете ли вы предоставить больше информации о том, что должно происходить в GetCommand в вашей реализации, действительно ли команда зависит от параметров, так как я не уверен, что из-за этого предложить для этого.

0 голосов
/ 11 марта 2009

Третий вариант - обнаружение возможных команд во время выполнения децентрализованным способом.

Например, Spring может сделать это в Java с помощью так называемого «сканирования пути к классам», отражения и аннотаций - Spring анализирует все классы в указанных вами пакетах, выбирает аннотированные @Controller, @Resource и т. Д. И регистрирует их как бобы.

Сканирование пути к классам в Java основывается на добавлении записей каталогов в архивы JAR (чтобы Spring мог перечислять содержимое различных каталогов пути к классам).

Я не знаю о C #, но там должна быть похожая техника: возможно, вы можете перечислить список классов в вашей сборке и выбрать некоторые из них на основе некоторых критериев (соглашение об именах, аннотации и т. Д.).

Теперь, это всего лишь третий вариант, чтобы иметь в виду, чтобы иметь в виду третий вариант. Я сомневаюсь, что это на самом деле должно быть использовано на практике. Ваша первая альтернатива (просто напишите фрагмент кода, который знает обо всех классах) должна быть выбором по умолчанию, если у вас нет веских причин поступить иначе.

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

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

Веселись!

0 голосов
/ 11 марта 2009

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

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