Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

воскресенье, 3 октября 2010 г.

Шаблоны: Strategy и Template method или мухи к мухам, котлеты к котлетам.

Strategy и Template method. Как оказалось я не раз использовал эти идеи, но только вчера из книги Мартина я связал этот свой опыт с двумя новыми названиями. Погуглив немного сегодня я понял, что простого описания не так просто найти. Попробую на пальцах (т.е. без кода) объяснить что понимаю сам. Что получится фиг его знает, но выговориться мне явно надо. Итак начнем (т.е. читать дальше)...

Жил был один метод, который делает нечто за 30 строк. Он оттестирован и работает отлично. Спустя некоторое время другим программистом пишется новый метод, для других целей (пускай в другом модуле), он тоже оттестирован и отправлен в реппозиторий. Так случилось, что ревьювером на этот раз был программист написавший первый метод. Он то и заметил сходство своего старого метода и метода нового. Программист-ревьювер владел рефакторингом, но познаний его в этой области явно было не достаточно. Как его учил дядюшка Фаулер - он нашел запах дублирования и попытался мысленно его устранить. А не вышло.

Если бы дублирование наблюдалось в одном месте, то его без проблем можно было бы Extract'нуть в отдельный метод, который использовался бы повторно. Но тут нечто другое - тут дублировался не сам код строчка в строчку, а общая идея - что делается методом: была некая инициализация данных, потом цикл, который проходит по только что инициализированному массиву вперед, после проверка, а потом цикл, делающий обратный проход и еще один цикл очистки массива на основании на условия.

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

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

Сомнений не было - раз уж эта абстрактная фигня дублируется везде, и даже приводит к появлению ошибок - значит что-то не так. Ее надо выделить! Но как? И они (уже втроем) пошли за помощью к Архитектору. Тот, не отрываясь от монитора, сказал - Template Method. Чего?! - спросили программисты? Выделите абстрактное поведение в родительский класс, а конкретику оставьте потомкам. Ясно - невнятно пробормотали программисты и отправились экспериментировать.

Эксперимент удался. В результате родился новый абстрактный класс в котором в чистом виде содержалась, уже порядком надоевшая "инициализация, цикл, условие, обратный цикл, цикл зачистки по условию". Вся конкретика, будь то условие или тело конструкций IF, тело циклов или алгоритм инициализации, осталась в исходных классах (теперь уже в наследниках новорожденного родителя) и была разнесена по отдельным методам. Эти методы были объявлены в родителе как абстрактные, а родитель ими пользовался, не заботясь о том, как и кем они будут реализованы.

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

Как-то одним весенним утром, внезапно, пришло письмо, в котором заказчик попросил переделать некую функциональность на новый лад. Собралась команда обсудить это новшество и немного поработав над кодом пришла к выводу, что часть классов надо переписать. Как ты уже наверное понял - изменения касались недавно разделенных методов, а именно в половине случаев там, где была "инициализация, цикл, условие, обратный цикл, цикл зачистки по условию" нужно было сделать почти то же, но в другом порядке. И это в половине случаев, не во всех, а в половине! А в одном классе, так вообще в зависимости от условия выбирать либо первый либо второй порядок.

Что теперь? Делать еще одного абстрактного родителя с новым поведением? Или в старом абстрактном родителе лепить IF и добавлять блок с новым поведением? Эти решения не нравилось никому. И тут наш старый добрый архитектор, все так же не отворачиваясь от своего компа, сказал - Strategy. Чего? - переспросили его. Вам поможет шаблон Стратегия - ответил Архитектор. Ясно... И ребята отправились гуглить, чего это за Стратегия такая? Название явно вселяло надежду, ибо им как раз надо было в зависимости от класса выбирать либо тот либо иной порядок.

Погуглив, программисты нашли описание шаблона Startegy и поняли, что именно в нем устраняется основной недостаток подхода Template method - тесное связывание конкретики с абстрактной частью. Что может быть крепче родственных связей наследования? Тут как в жизни: папа не всегда знает всех своих детей, а дети навсегда остаются биологически связаны с одним конкретным папой. Стратегия же позволяет устранить эту связь используя принцип инверсии зависимостей (Dependency Inversion) с введением дополнительного интерфейса. Теперь дети реализуют интерфейс Дети, а Папы умеют няньчится с Детьми. Ни о каком родстве речи не идет. Любой Папа и любой Дитё умеют... Хотя нет, тут лучше использовать понятие Мама (для Пап больше подходит другое абстрактное правило...). Так вот, любой Дитё и любая Мама могут поладить друг с другом пользуясь одним понятием Ребенок (он же Дитё).

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

Счастливый конец.

Почитать про Dependency Inversion можно так же в статье "Инверсия зависимостей при проектировании Объектно-Ориентированных систем".

Комментариев нет:

Отправить комментарий