В обсуждении одного из моих ответов на ru.stackoverflow в G+ был поднят вопрос по поводу того, является ли оператор switch design или code smell-ом?
Тут нужно быстро вспомнить, откуда ноги вообще растут (ИМХО). В наших с вами разных языках программирования существует много разных способов решения одной и той же проблемы. Например, когда у нас есть определенная задача (нарисовать фигуру?!) и несколько разновидностей входных данных (круг, квадрат, прямоугольник?), то решить ее можно разными способами. Можно взять впихнуть тип в структурку и в методе draw перебрать все возможные варианты.
С точки зрения серьезного объектно-ориентированного программиста, такой подход считается негодным, нерасширяемым и вообще, неправильным. Ибо об этом писал дядюшка Боб Мартин, который будет потом приходить по ночам в страшных снах и мучить бедных программистов страшными вещами, типа добавлением новой фигуры каждые 42 минуты.
Чтобы следовать чьим-то советом правильно, а не как обычно, нужно понимать контекст, в котором этот самый совет дается.
Так, например, проблема с фигурками и их рисованием заключается в том, что в будущем *предполагается* добавление новых фигур и не предполагается добавление новых операций над этими фигурами. В этом случае, для уменьшения стоимости внесения изменений и дается совет по выделению иерархии наследования.
Но даже в этом случае нужно понимать, на какой компромисс мы идем: иерархия наследования позволяет легко добавить новый тип фигуры (минимум изменений), но усложняет добавление новых операций (много изменений во множестве типов). В этом плане подход на основе объектов упрощает изменение системы в одну сторону, а тот же структурный или функциональный подход – в другую (в этом случае проще будет добавлять именно новую операцию, а не новый тип). Эта проблема широко известна под названием Expression Problem, а сравнение ФП и ОО подходов я делал в статье OCP: ФП vs. ООП.
Ну, ок, с фигурками мы разобрались, а как насчет других случаев использования оператора switch? Ведь он не расширяемый и нам, о ужас, придется изменять код при изменении требований?
Тут, как всегда, все зависит от контекста, решаемой задачи и того, что в этом switch-е происходит.
Вот, например, если у вас есть фабрика, которая создает экземпляр некоторого объекта по входным данным, например, некоторый парсер в зависимости от расширения файла:
public class ParserFactory
{
public static IParser CreateParser(string fileName)
{
switch (Path.GetExtension(fileName))
{
case ".txt":
return new TextParser();
case ".xml":
return new XmlParser();
default:
throw new NotSupportedException();
}
}
}
Кто-то скажет, что решение #нерасширябельное, нарушает SRP и вообще, никуда не годится.
На самом деле, сделать такой вывод лишь по этому фрагменту нельзя. Если подобный switch лишь один, то с кодом все в порядке. Бертран «Я могу и ФП тоже» Мейер назвал это принципом единственного выбора, который заключается в следующем: если некоторый выбор находится в одном месте, то код зашибись. Если он начинает растекаться по коду, то это плохо и нужно что-то менять.
Но просто подумайте, а какие здесь альтернативы: прикрутить полиморфизм? Фабрику? А кто будет создавать фабрику? Что, MEF прикручивать и саморегистриуемые компоненты писать? Да, а крыша не поеедет это все сопровождать? Да, можно прикрутить словарик, но даже в этом случае он уместен, когда кода на каждый кейс много или самих кейсов сильно больше 2-х или 3-х.
Любые дополнительные уровни косвенности, они же хороши тогда, когда каждый случай сложен. А замена switch-а полиморфизмом уместна лишь тогда, когда иерархия наследования вырисовывается из домена и когда логики на каждый кейс становится сильно больше пары операторов.
Ну и самое главное, изменения требований приведут к изменению кода. Это нормально. Код, который будет расширяться как-то хитро путем дописывания наследников, передачей коллбеков или другим хитрым способом – это усложнение, которое нужно вносить тогда, когда нужно, а не с самого начала.
Городить иерархию классов вместо switch, это видимо кого-то книги "Мартина" слишком сильно затянули, безвозвратно. Ещё вспоминается байка про Фаулера, Бека и рефакторинг "Замена паттерна Стратегия оператором if" :)
ОтветитьУдалитьПо своему опыту, если switch уникален и я его пишу в первый раз - порядок. Если дописываю или начинает более чем в одном месте использоваться - по ситуации - обычно либо Dictionary, либо "стратегии". Но до последнего очень редко доходит.
+100 500!
УдалитьА что за байка?
УдалитьВам это байка, а вот у человека это суровая реальность - http://www.yegor256.com/2015/08/18/multiple-return-statements-in-oop.html
Удалить>Если дописываю или начинает более чем в одном месте использоваться - по ситуации - обычно либо Dictionary, либо "стратегии".
ОтветитьУдалитьЛибо, наверное, вынес switch-а в отдельный метод?
Тоже вариант. Правда всё равно туда приедут в параметр лямбды или что-то подобное (раз уж switch у нас не в одном месте, значит должна работать разная логика), а как только с этим связываешься, задумываешься про Dictionary. По крайней мере у меня так :)
Удалить+++!
ОтветитьУдалитьПолностью согласен!
Полностью согласен с авторами ответов. У автора на stackoverflow изначально неверные требования, к расширению. Если речь в расширении идет всего лишь о методе, то это слишком узкий взгляд и надо либо смотреть шире либо думать о расширении, когда в этом будет прямая необходимость. А так это пустая трата мыслей и времени.
ОтветитьУдалитьА вот как быть со свитчом с паттерн матчингом?
ОтветитьУдалитьМеня в нынешних switch-ах очень смущает необходимость писать default реализацию, по той причине, что если я добавляю какую-либо ветку (например, новое значение энума), то я вынужден сам шерстить код в поиске мест где надо добавить логику.
УдалитьК сожалению, в новой версии шарпа паттерн-матчинг "кривоват", в плане того что алгебраических типов данных (discriminated union-ов) там не будет. И по прежнему надо реализовывать default ветку.
Т.е. никакой помощи от компилятора ждать не приходится :(
ВОт это мне всегда нравится :) т.е. новое значение добавляем, а просмотреть что делает код по старым значениям не, не надо?
Удалитьвообще я думаю в идеале этого хотелось бы избегать
УдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьМартин всё ж таки не даёт тебе покоя. Троллишь его через раз :)
ОтветитьУдалитьНу, есть такое:))
Удалить"а крыша не поеедет"
ОтветитьУдалитьопечатка