Zope3 Component Architecture: children adapter

Опубликовано апр 2008г. здесь

Недавно tretiy3 обмолвился, что тем, кто первый раз увидел Zope3 и задает вопросы на форумах, нехватает простых примеров и разъяснений на пальцах. Сейчас появился отличный русскоязычный сайт для желающих изучить Zope3 / ZCA (Zope Component Architecture) - http://zopelada.ru. Там много всяких примеров разной степени сложности и наглядности. И отличные лекции.

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

Итак, представим компонентно-ориентированную систему как разнородное и несвязанное множество зверюшек в лесу, многие из которых не имеют ничего общего друг с другом.

Пример приводится в интерактивной сессии Python с доступными для импорта пакетами zope.interface и zope.component. Регистрация компонент в глобальных реестрах в условиях сервера приложений делается с помощью ZCML-директив, и это единственное, для чего они нужны, в действительности. Здесь же регистрировать будем напрямую, используя zope.component.provideAdapter для глобального реестра:

>>> from zope.interface import Interface, Attribute, implements
>>> from zope.component import provideAdapter, adapts

Задача - задать каждой зверюшке один и тот же вопрос: "Мокрый ли у тебя нос ?". Большинство тварей ответят "да" или "нет". Нарисуем общий интерфейс для большинства зверюшек:

>>> class INoseAware(Interface):
...
...     soppy = Attribute(u'Мокрый ли нос (boolean)')
...
...     def isSoppyNose():
...         """ Метод дает ответ на вопрос о мокроте носа"""

Реализация интерфейса не имеет значения. Главное, что при приведении к нему любого объекта, предоставляющего этот же интерфейс, ошибки не будет.

Для примера возьмем собаку и льва:

>>> class NoseAware(object):
...
...    implements(INoseAware)
...
...    def __init__(self, soppy):
...         self.soppy = soppy
...
...    def isSoppyNose(self):
...         print self.soppy and u'Да' or u'Нет'
...
>>> dog = NoseAware(True)
>>> lion = NoseAware(False)

И попробуем провести такую операцию:

>>> INoseAware(dog).isSoppyNose()
Да
>>> INoseAware(lion).isSoppyNose()
Нет

Это приведение к интерфейсу, и относится к нему в данном случае можно также, как к приведению типов (часто встречающемося в коде на Java и других языках со статической типизацией.) Правда, в данном случае, "тип" уже тот который нужен :)

Теперь - Слон. У него нет носа, только хобот. (анатомические споры оставим для ботаников.) На вопрос "Мокрый ли у тебя нос ?", слон впадает в транс traceback. Опишем интерфейс для слона:

>>> class IHobot(Interface):
...
...     length = Attribute(u'Длина хобота')

Реализация интерфейса опять-таки не имеет никакого значения. Ясно, что он не имеет ничего общего с INoseAware? и операция вида

>>> INoseAware(elephant).isSoppyNose()

(где elephant - экземпляр класса, реализующего интерфейс IHobot?), приведет к ошибке.

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

>>> class HobotToNose(object):
...
...     implements(INoseAware)
...     adapts(IHobot)
...
...     def __init__(self, context):
...         self.context = context
...
...     def isSoppyNose(self):
...         print "Нет у меня никакого носа и я не знаю что это такое."
...         print "Зато есть хобот длиной %s метров." % self.context.length

B зарегистрируем его в глобальном сайт-менеджере:

>>> provideAdapter(HobotToNose)

Все. Теперь можно посмотреть, что все работает, и при преведении объекта-слона к чуждому интерфейсу автоматически отыскивается адаптер HobotToNose?:

>>> class Hobot(object):
...
...     implements(IHobot)
...
...     def __init__(self, length):
...         self.length = length
...
>>> elephant = Hobot(5)
>>> INoseAware(elephant).isSoppyNose()
Нет у меня никакого носа и я не знаю что это такое.
Зато есть хобот длиной 5 метров.

Мда. Чушь какая-то получилась. Но это действительно простой пример адаптера.