Aug 03, 2013 01:55
Дело было так. Я читал книжку «Типичные ошибки проектирования» Эрика Аллена, в которой он, в частности, пишет, что плохо заводить в классе поля, используемые не во всех его экземплярах. А также не менее плохо заводить специальное поле, которое определяет поведение класса, что-то вроде его типа (не помню, как это явление называлось у автора, а книжку уже вернул).
В этот момент я редактировал мое перечисление Link, которое обладало букетом полей, заполнявшихся или не заполнявшихся в зависимости от типа ссылки. И как раз собирался добавить поле, которое этот тип ссылки задает (то есть планировал удвоить успех). Как же тогда быть c этим великолепием? Разумеется, здесь нужно выделить три классика для хранения проблемных полей, чтобы каждый классик имел свой набор полей и свое поведение. Классики реализуют общий интерфейс Reference, чтоб можно было завести поле в исходном перечислении Link.
Кроме того, чтобы завести поле reference, с него неплохо бы получить какую-нибудь пользу, то есть уметь делать с ним какую-нибудь операцию. Например, разыменование ссылки. Предположим, что операция тащит много зависимостей, и поэтому мы не хотим реализовывать ее внутри ссылки. А как же тогда ее реализовать, если никаких общих полей в интерфейсе нет? Очевидное и неудачное решение - instanceof. А удачное решение - (та-да!) Visitor. Объявляем интерфейс (например, Resolver), в котором есть наша операция (например, resolve) для каждого вида ссылок (то есть три операции resolve в нашем случае). Он-то и будет ответственным за выполнение операции над полями всех трех видов ссылок. Затем добавляем в исходный интерфейс Reference единственную операцию resolve(Resolver r), которая принимает в качестве параметра наш новосозданный Reslover. Осталось только вызывать нужный из трех вариантов операции resolve объекта Resolver в каждом из трех наследников Reference.
interface Reference {
resolve(Resolver r)
}
interface Resolver {
resolve(RegionLinkReference r)
resolve(ConfiguredLinkReference r)
resolve(HardcodedLinkReference r)
}
class SomeLinkReference implements Reference {
...
resolve(Resolver r) {
r.resolve(this)
}
}
Готово: мы избежали свитчеобразных if, а также instanceof и получили OO код. В нем легко добавлять новые операции (достаточно объявлять новые классы, реализующие Resolver), но сложно добавлять новые виды ссылок. Что, как мы помним, в обычной ситуации характерно для процедурного когда, но не объектного (см. предыдущий пост). Именно для этих целей и служит паттерн визитор, и его область применения не ограничивается традиционными деревьями и коллекциями.
Что же побудило меня написать такой длинный пост про всем давно известный паттерн? Дело в том, что это первый в моей жизни случай осмысленного применения паттерна. Не считая конечно деревьев, синглтонов и статических недофабричных методов, имеющих мало чего общего с одноименным изобретением GoF.
(Кстати, вспоминая предыдущий пост: возвращать null Эрик конечно тоже не разрешает, так же как и держать неинициализированные поля, в которых остается нулевая ссылка.)
дни