В условиях иерархий объектов часто возникает ситуация, когда необходимо определить поведение или состояние в зависимости от типа объекта. Т.е. если объект типа А, то нужно сделать то, если типа Б, то другое, а если типа Ц, то вообще ничего делать не нужно. В таких случаях некоторые программисты пишут невероятно красивый код, например:
if (someObject is IFirstInterface)
DoThat();
else if (someObject is ISecondInterface)
DoThis();
else if (someObject is IThirdInterface)
DoAnotherThing();
...
else if (someObject is IOneThousandSixHundredsSixtySixthInterface)
DoSomethingAbsolutelyAmazing();
else
DoNothing();
Однако, как оказывается, кроме красивой лесенки из if'ов и else'ов ничего хорошего в этом коде нет :( При появлении нового интерфейса (1667-го по счету) придется залезть в этот метод и добавить еще один else if. Ну и понятно, что тут возникают проблемы с поддержкой, сопровождением, отлавливанием багов и кучей прочих неприятных вещей. Посему предлагаю совместно искать выход из сложившейся критической ситуации! Вот, например, пара выходов.
Вариант #1:
Выносим определение метода в интерфейс и заставляем всех, чей тип мы проверяли, реализовывать этот интерфейс.
public interface IDoer // Отличное название, не так ли? :) От слова do
{
void Do();
}
public interface IFirstInterface : IDoer {}
public interface ISecondInterface : IDoer {}
Тогда наша лесенка сложится всего в одну ступеньку:
if (someObject is IDoer)
someObject.Do();
else
DoNothing();
Достоинства: Не создается никаких дополнительных классов. При добавлении нового класса он просто реализует в себе необходимое действие.
Недостатки: В класс может вноситься чуждая ему логика, могут понадобиться какие-то внешние данные.
Вариант #2:
Создаем класс-мэппер типа (или интерфейса) на класс, инкапсулирующий необходимое действие
public class TypeToCommandMapper
{
private Dictionary<Type, ICommand> _typeToCommandMap;
public ICommand GetCommand(Type type)
{
if (!_typeToCommandMap.ContainsKey(type)) //
return new EmptyCommand();
return _typeToCommandMap[type];
}
}
И теперь у нас совсем нету лесенки:
ICommand command = typeToCommandMapper.GetCommand(someObject.GetType());
command.Execute();
Достоинства: Вновь появляющиеся классы не обременяются лишней логикой, не должны реализовывать какой-либо специфичный интерфейс
Недостатки: При появлении нового класса необходимо также обновить и класс-мэппер. Но, в принципе, определение мэппинга можно вынести в отдельный файл конфигурации и добавлять новые значения туда, без перекомпиляции. Новую команду придется реализовывать в любом случае (если конечно она нужна :))
Есть ли у кого-то еще варианты решения такой проблемы?
P.S. Какой-то длинный вопрос получился, на всякий случай шлепнул галку "Риторический вопрос" :)
P.P.S. Убрал галку, а то никто не ответит больше :)
P.P.P.S. Чьорт, в обычный вопрос не умещаюсь, снова поставил галку :)

Мне нравится вариант #2. Вполне объектно-ориентированно и соответствует всем принципам. Потому что при появлении нового элемента иерархии мы добавляем команду, а не изменяем какой-то старый код. Можно еще использовать метаданные для облегчения меппинга. Например, помечаем команду атрибутом
а меппер автоматически по таким атрибутам сопоставляет команды с элементами иерархии.