Дизайн-шаблон Visitor из портфолио GoF

Сегодня у меня возникло желание поделиться с вами знаниями о шаблоне проектирования Visitor (Посетитель).

Некоторые источники называют его “продвинутой командой”, что недалеко от правды 😉 , из того же каталога GoF. Поэтому знания Command вам сегодня очень пригодятся.

Свое объяснение я построю на примере расширения функциональности файловой системы, позаимствованном мной из книги Влиссидиса “Pattern Hatching: Design Patterns Applied”.

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

Предыстория: давайте представим себе диаграмму классов для абстрактной файловой системы (ФС). С одной стороны, мы должны обеспечить универсальность работы со всеми элементами ФС, а с другой, обеспечить правильную работу с каждым из ее элементов в ходе выполнения абсолютно реальных операций. Чтобы было понятней, постарайтесь ответить на вопрос: “Могу ли я создать новую директорию в файле?” В результате проектирования у нас получиться что-то напоминающее нижеследующую диаграмму классов. Тут нам и древовидная структура хранения данных и возможность работать с каждым элементом файловой системы унифицировано:

Диаграмма классов файловой системы

Диаграмма классов файловой системы

Кстати, вы должны были бы узнать в этой диаграмме еще два шаблона! Не узнали?! Тогда вам нужно срочно повторить знания Композита (Composite) и Прокси (Proxy).

Итак, у нас с вами есть файловая система т.е. те голые данные которые безусловно важны для нас, но совершенно бесполезны если мы не можем ими манипулировать. Возникает вопрос: где же место для тех команд, с помощью которых мы и хотим манипулировать ФС?! Помните: ls/dir, mkdir, rm, cat, tail, move, copy, ln, etc. Давайте для начала ограничимся узким набором команд, например: dir, mkdir, rm, copy. Я не случайно остановился на этом наборе – некоторые, из приведенных команд, отлично работают и с файлами и с директориями ФС, а некоторые не имеют смысла для конкретных элементов ФС 😉 .

Сделаем первое предположение: “Давайте разместим команды в виде методов интерфейса Node и проанализируем это решение с точки зрения дизайна нашей системы”. В результате у нас получится следующий вариант диаграммы классов:

Диаграмма классов ФС, нарушающая "Open-Close Principle"

Диаграмма классов ФС, нарушающая “Open-Close Principle”

Теперь давайте взвешивать все “за / против”. С одной стороны подобный дизайн позволяет работать и с файлом и с директорией унифицировано, т.к. этот контракт будет распространятся на всех наследников. Естественно, что нам придется в классе File “глушить” метод mkdir(), но это уже совсем другая история, главная цель достигнута – ставим себе плюсик.

Теперь давайте посмотрим на наш дизайн с другой стороны – вы можете представить в файловой системе что-нибудь отличное от файла и директории (ну и еще линка в придачу)? Думаю – нет!? А вот какое количество команд существует в файловой системе?! Я думаю, что врядли кроме разработчика конкретной OS найдется человек, который сможет на память перечислить все существующие команды, не говоря уже об особенностях конкретной операционки. Чувствуете к чему я клоню?! По мере того как мы будем разрабатывать новые команды, присутствующие в нашей ФС, нам придется расширять контракт интерфейса Node, а значит… значит и модифицировать всех его наследников. А вот это уже серьезный “минус” – ведь это явное нарушение “Open-Close Principle“. В этом случае система будет открыта к модификации и закрыта к расширению, а ведь мы хотим получить как раз обратный эффект. Минус явно перевесил плюсы, а значит подобное решение нам не подходит.

Идем дальше, давайте разнесем команды по наследникам:

Диаграмма классов ФС, нарушающая "Liskov Substitution Principle"

Диаграмма классов ФС, нарушающая “Liskov Substitution Principle”

В этом случае мы получаем проблемы с унифицированным использованием компонент ФС клиентом. Это видно не вооруженным глазом – у Dir и File разные контакты, а это значит, что клиент не сможет работать с ними как с Node везде где будут встречаться файлы и директории 🙁 . А это значит, что приведения типов и “instanceof-ов” нам, увы, не избежать.

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

Продолжение следует…

This entry was posted in Java and tagged , , . Bookmark the permalink.