Улыбка без кота

13 апрель, 2006 - 23:00Виктор Вейтман

Предложите специалисту по электронной технике спроектировать простейшее устройство, но поставьте условие: надо самостоятельно разработать и рассчитать схему, выплавить кремний для транзисторов, синтезировать пластмассу для корпуса – в общем, сделать все с нуля. Догадываетесь, что он вам ответит? А между тем многие программисты берутся за подобную работу, ни на минуту не сомневаясь в успехе. И после недоумевают, почему результат оказался столь плачевным.

– Хорошо, – сказал Кот и исчез – на этот раз очень медленно. Первым исчез кончик его хвоста, а последней – улыбка; она долго парила в воздухе, когда все остальное уже пропало.
Льюис Кэрролл

При написании программ обычно используют библиотеки. Мы уж давно привыкли к ним, и кажется вполне естественным, что опыт наших предшественников воплощен именно в выверенном работоспособном коде. Но что делать, если найдено изящное решение, которое невозможно (или сложно) оформить в виде объекта или даже функции, а программа, в которой оно реализовано, совершенно не подходит для повторного использования? Приходится иметь дело с «чистыми идеями», оторванными от конкретного «программного носителя». В других областях это обычная практика: ведь никому не придет в голову потребовать, например, чтобы принципы, лежащие в основе какого-либо устройства, обязательно были воплощены в металле. Их вполне можно перенести на бумагу в виде чертежа, схемы или набора инструкций. Программисты же, избалованные относительной простотой создания кода, далеко не всегда применяют даже обычные блок-схемы. Однако в их распоряжении имеются довольно развитые способы представления абстрактных решений, один из которых носит название образы разработки (design pattern). К сожалению, эта концепция знакома не всем программистам-практикам.

Design pattern

К сожалению, до сих пор не существует однозначного перевода термина design pattern на русский язык. В литературе и Интернете можно встретить множество различных вариантов, от «шаблона проектирования (или программирования)» до «паттернов». Ни один из них, пожалуй, нельзя признать вполне удачным, например, первый явно перекликается с шаблонами в смысле templates. Интерпретация же, избранная автором статьи, видимо, призвана подчеркнуть некую «образность» данной концепции (Прим. ред.).

Но это же тривиально!

Вряд ли есть на свете что-нибудь скучнее формальных определений. Да и, как показывает опыт, они далеко не всегда помогают понять суть. Другое дело – конкретный пример, пусть даже предельно упрощенный.

Предположим, мы создаем некую программу, предназначенную для различных платформ. К счастью, в нашем распоряжении есть Java и другие подобные языки, обеспечивающие реальную переносимость кода. Но одна деталь немного портит картину. В процессе работы программа должна извлекать конфигурационные параметры, а они в одном случае находятся в файле configure, в другом – в файле setup. Мелочь, а все-таки неприятно. Придется проверять, с какой системой мы имеем дело и, в зависимости от результатов, обращаться к тому или иному файлу. А чтобы не создавать неудобств в процессе работы, желательно «спрятать» этот код в отдельном методе и пользоваться им по мере необходимости. В дальнейшем о том, как именно реализован метод, можно вообще забыть. Именно такое решение и подсказывает образ разработки под названием Façade.

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

Не спешите выполнять приказ: вдруг его отменят

Пусть на этот раз нашей задачей будет создание редактора для документов, включающих в себя различные объекты, в частности видеофрагменты. Понятно, что для получения большого объема данных потребуется некоторое время, а если загрузка осуществляется по сети, то и весьма немалое. Пользователю же хочется поскорее приступить к работе, не ожидая видеофрагмента, который может и не понадобиться. Такое желание вполне понятно нам, разработчикам, и мы не против пойти ему навстречу. Надо отобразить документ сразу после загрузки текста, вместо видеофрагментов оставить пустые «контейнеры» и подгружать данные лишь по требованию пользователя. Сказано – сделано. Мы открываем редактор... Где там часть кода, отвечающая за загрузку видеоинформации?

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

Решение подскажет нам образ разработки Proxy (иногда его называют Surrogate). Вместо того чтобы реализовывать новые функции в коде редактора, давайте поместим их в соответствующие вспомогательные классы. Конкретнее – заменим объекты видеофрагментов их «представителями». Последние зарезервируют место на экране, по команде пользователя начнут загрузку требуемых данных, одним словом, будут полностью контролировать видеофрагменты. Реализация же подобных представителей – это, как говорится, дело техники.

Герой должен быть один

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

Выясняется, что, если перейти в секцию «Литература» именно из секции «Расходные материалы», создается новый экземпляр «корзинки» покупателя, в результате все заказы, сделанные ранее, теряются. Кто-то из программистов не подумав использовал оператор new, который, как оказалось, получает управление лишь при определенном стечении обстоятельств. Подобный казус не случился бы, позаботься разработчики о том, чтобы в рамках сеанса можно было создать лишь один экземпляр класса, представляющего «корзинку». И сделать это несложно: надо всего лишь запретить доступ к конструктору класса, а взамен предоставить get-метод. В коде последнего нужно проверить, есть ли «корзинка», и, если она уже существует, не создавать новую. Другими словами, применение образа разработки Singleton позволило бы избежать ошибки, проявившейся только в процессе эксплуатации продукта.

Позаботимся о будущем

Чем проще программа, тем больше шансов, что в ней не будет ошибок. Может быть, в таких случаях образы разработки излишни? Очередной пример позволит убедиться, что и это не так. Пусть создаваемый нами код имитирует зажигание электрической лампочки – инкапсулируем его в классе Bulb. Еще один класс – Key – описывает переключатель. Других участников взаимодействия попросту нет. В Key мы создаем экземпляр Bulb, а по команде, изменяющей положение переключателя, зажигаем или гасим лампочку. (Приводить код нет необходимости, каждый читатель легко напишет его на любом удобном языке.) Вроде бы все предельно просто, и кажется, что ошибиться невозможно: программа блестяще проходит тестирование. Можно принимать поздравления?

Рановато. Несмотря на то что явные ошибки отсутствуют, мы нарушили сразу два принципа объектного проектирования. Во-первых, класс Key зависит от класса Bulb, так как в нем создается экземпляр последнего. В результате любое преобразование Bulb, даже переименование его в Lamp, затронет Key. Во-вторых, если нам надо будет включать и выключать не лампочку, а, скажем, электродвигатель, придется исправлять код Key, заменяя в нем Bulb на Motor. Каждая последующая модификация потребует все больших усилий, и в конце концов код станет полностью неконтролируемым.

Исключить влияние класса Bulb на класс Key позволит образ разработки Abstract Server. Объявим интерфейс с именем, например Switchable. Его будет реализовывать класс Bulb, а конструктору Key вместо объекта Bulb передадим объект Switchable. (Данное решение типично для Java. В C++ придется создать дочерний класс, возможно, применить множественное наследование, но суть от этого не меняется.) Теперь переключатель сможет управлять не только лампочкой, но и электроплитой, телевизором, т. е. любым прибором, который включается или выключается. И конечно же, изменения в классе Bulb никак не коснутся Key.

Кому нужны образы разработки

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

И наконец, нельзя забывать еще об одном важном свойстве образов разработки. У них есть... имена. А это совсем не пустяк! Если программное обеспечение разрабатывается силами рабочей группы (в большинстве случаев это именно так), то немало времени тратится на обсуждение основных решений. Но когда вместо «Давайте включим объект A в состав вспомогательного объекта, который для внешнего наблюдателя будет полностью имитировать объект B» мы сможем сказать «Давайте применим образ разработки Adapter», разговор наверняка станет более конкретным.

Так что же, образы разработки – всем и каждому? На этот счет могут быть разные точки зрения. Наверное, стоит принять к сведению мнение одного из основных идеологов данной концепции, Эрика Гамма (Erich Gamma). Он считает, что для начинающего программиста пытаться применить образы разработки – безнадежное занятие. Лишь получив опыт практической работы и набив пару шишек, специалист будет готов к тому, чтобы внимательнее прислушиваться к предшественникам.

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

Где же можно подробнее ознакомиться с образами разработки? Разумнее всего начать с классики – книги Design Patterns Elements of Reusable Object-Oriented Software Эрика Гамма и трех его соавторов, известных как «банда четырех» (Gang of Four). Написанная более десяти лет назад, она до сих пор пользуется популярностью, однако большинству отечественных читателей недоступна. Поэтому придется поискать другой источник. Неплохой отправной точкой может служить работа Design Patterns: From Analysis to Implementation. В ней содержится таблица с именами и краткими описаниями наиболее популярных образов разработки. А далее для получения подробной информации о конкретных программных решениях каждый желающий может воспользоваться Всемирной Сетью и многочисленными поисковыми системами.

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