Так получилось, что за последний месяц меня несколько раз спрашивали, как бы упростить сложную конструкцию из условных операторов. Все разы я отсылал людей к паттерну Strategy, но в ответ получал: "это слишком сложно". Тут я задумался - люди вроде не новички, может я чего не то говорю? Залез в собственный код и обнаружил, что в чистом виде Strategy практически никогда не использую, а делаю некоторое его упрощение.
Итак, как же упростить условную логику и не прибегать к созданию полноценной стратегии?
Вместо вымышленных абстрактных примеров возьму "максимально приближенный к реальности". Предположим, что у нас есть набор стилей для некоторого ascx контрола. Стиль может меняться в зависимости от прав пользователя, его личных настроек и т.п. - сейчас это не принципиально. Важно то, что существует несколько параметров, которые влияют на выбор стиля.
Для выполнения задачи было создано несколько классов, реализующих некий интерфейс, а для получения нужного типа был создан вот такой метод:
[code language="C#"]
public interface IComplexControlStyle {
// Некоторый набор методов
}
public IComplexControlStyle GetStyle(bool animated, bool editable, bool checkFormat) {
if (editable) {
if (animated) {
if (checkFormat) {
return new EditableAnimatedFormattedStyle();
} else {
return new EditableAnimatedUnformattedStyle();
}
} else {
if (checkFormat) {
return new EditableStaticFormattedStyle();
} else {
return new EditableStaticUnformattedStyle();
}
}
} else {
if (checkFormat) {
if (animated) {
return new LockedAnimatedFormattedStyle ();
} else {
return new LockedStaticFormattedStyle();
}
} else {
return new LockedStaticUnformattedStyle();
}
}
}
[/code]
Чтобы не было вопроса "а нельзя ли задать то всё по отдельности?", дам небольшое пояснение:
- EditableAnimatedFormattedStyle - текстовые поля посвечиваются (различными цветами) при наведении и фокусе, а также при наличии ошибок (при наведение на поля с ошибками и без ошибок цвета подсветки разные)
- EditableAnimatedUnformattedStyle - текстовые поля посвечиваются (различными цветами) при наведении и фокусе
- EditableStaticFormattedStyle - рядом с текстовыми полями с ошибками появляется красный треугольник
- EditableStaticUnformattedStyle - никаких опозновательных знаков для текстовых полей
- LockedAnimatedFormattedStyle - вместо текстовых полей рисуется текст, неправильно отформатированный текст помечен мерцающими рамками
- LockedStaticFormattedStyle - вместо текстовых полей рисуется текст, неправильно отформатированный текст помечен красным шрифтом
- LockedStaticUnformattedStyle - вместо текстовых полей рисуется текст без всяких пометок
Как видим, все параметры достаточно тесно друг с другом связаны. Пытаться разделить их - себе дороже. C другой стороны, код явно выглядит не слишком удобочитаемым, в нем при тех или иных изменениях легко допустить ошибку. Кроме того, если добавится ещё один параметр, то станет совсем неприятно.
Тем не менее, паттерн Strategy в чистом виде применять не хочется - слишком много придется вносить изменений. Поэтому мы поступим проще.
- Напишем маленький метод, переводящий набор булевских значений в битовую маску. Собственно, такой метод много где бывает полезен, поэтому я его к рефакторингу не отношу (у меня он, как правило, есть в каждом новом проекте, притом в 3-х версиях - на C#, JavaScript и SQL):
[code language="C#"]
public static int GetMask(params bool[] bits) {
if (bits.Length > 32) {
throw new OverflowException();
}
int mask = 0;
for (int j = 0; j < bits.Length; j++) {
if (bits[j]) {
mask += (1 << j);
}
}
return mask;
}
[/code]
- Объявим несколько вспомогательный констант и сделаем метод для получения фабрик экземпяров класса, а также делегат для него:
[code language="C#"]
private const bool Editable = true, Animated = true, CheckFormat = true;
private const bool Locked = false, Static = false, NoFormatCheck = false;
private delegate IComplexControlStyle GetStyleDelegate();
private static GetStyleDelegate GetStyleFactory<T>() where T: IComplexControlStyle, new() {
return delegate { return new T(); };
}
[/code]
- Создадим словарь, ставящий в соответствие набору параметров нужный класс стиля:
[code language="C#"]
Dictionary<int, GetStyleDelegate> styles = new Dictionary<int, GetStyleDelegate>();
styles[GetMask(Editable, Animated, CheckFormat)] = GetStyleFactory<EditableAnimatedFormattedStyle>();
styles[GetMask(Editable, Animated, NoFormatCheck)] = GetStyleFactory<EditableAnimatedUnformattedStyle>();
styles[GetMask(Editable, Static, CheckFormat)] = GetStyleFactory<EditableStaticFormattedStyle>();
styles[GetMask(Editable, Static, NoFormatCheck )] = GetStyleFactory<EditableStaticUnformattedStyle>();
styles[GetMask(Locked, Animated, CheckFormat)] = GetStyleFactory<LockedAnimatedFormattedStyle>();
styles[GetMask(Locked, Animated, NoFormatCheck )] = GetStyleFactory<LockedAnimatedFormattedStyle>();
styles[GetMask(Locked, Static, CheckFormat)] = GetStyleFactory<LockedStaticFormattedStyle>();
styles[GetMask(Locked, Static, NoFormatCheck )] = GetStyleFactory<LockedStaticUnformattedStyle>();
[/code]
- Меняем исходный метод:
[code language="C#"]
public IComplexControlStyle GetStyle(bool animated, bool editable, bool checkFormat) {
return styles[GetMask(animated, editable, checkFormat)]();
}
[/code]
Вот, собственно, и всё. Единственный серьезный минус подхода в том, что приходится задавать вручную абсолютно все варианты. Лично я знаю 2 способа (один совсем простой, второй посложнее, но открывает новые возможности) как от этой необходимости избавиться. Желающие могут предложить свои в комментариях.
P.S. Все выше изложенное неплохо абстрагируется в отдельный класс, который затем можно многократно использовать в своем приложении для упрощения условных конструкций.