| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- ===============================
- Использование реестра адаптеров
- ===============================
- Данный документ содержит небольшую демонстрацию пакета ``zope.interface`` и его
- реестра адаптеров. Документ рассчитывался как конкретный, но более узкий пример
- того как использовать интерфейсы и адаптеры вне Zope 3.
- Сначала нам необходимо импортировать пакет для работы с интерфейсами::
- >>> import zope.interface
- Теперь мы разработаем интерфейс для нашего объекта - простого файла. Наш файл
- будет содержать всего один атрибут - body, в котором фактически будет сохранено
- содержимое файла::
- >>> class IFile(zope.interface.Interface):
- ...
- ... body = zope.interface.Attribute(u'Содержимое файла.')
- ...
- Для статистики нам часто необходимо знать размер файла. Но было бы несколько
- топорно реализовывать определение размера прямо для объекта файла, т.к. размер
- больше относится к мета-данным. Таким образом мы создаем еще один интерфейс для
- представления размера какого-либо объекта::
- >>> class ISize(zope.interface.Interface):
- ...
- ... def getSize():
- ... 'Return the size of an object.'
- ...
- Теперь мы должны создать класс реализующий наш файл. Необходимо что бы наш
- объект хранил информацию о том, что он реализует интерфейс `IFile`. Мы также
- создаем атрибут с содержимым файла по умолчанию (для упрощения нашего
- примера)::
- >>> class File(object):
- ...
- ... zope.interface.implements(IFile)
- ... body = 'foo bar'
- ...
- Дальше мы создаем адаптер, который будет предоставлять интерфейс `ISize`
- получая любой объект предоставляющий интерфейс `IFile`. По соглашению мы
- используем атрибут `__used_for__` для указания интерфейса который как мы
- ожидаем предоставляет адаптируемый объект, `IFile` в нашем случае. На самом
- деле этот атрибут используется только для документирования. В случае если
- адаптер используется для нескольких интерфейсов можно указать их все в виде
- кортежа.
- Опять же по соглашению конструктор адаптера получает один аргумент - context
- (контекст). В нашем случае контекст - это экземпляр `IFile` (объект,
- предоставляющий `IFile`) который используется для получения из него размера.
- Так же по соглашению контекст сохраняется а адаптере в атрибуте с именем
- `context`. Twisted комьюнити ссылается на контекст как на объект `original`.
- Таким образом можно также дать аргументу любое подходящее имя, например
- `file`::
- >>> class FileSize(object):
- ...
- ... zope.interface.implements(ISize)
- ... __used_for__ = IFile
- ...
- ... def __init__(self, context):
- ... self.context = context
- ...
- ... def getSize(self):
- ... return len(self.context.body)
- ...
- Теперь когда мы написали наш адаптер мы должны зарегистрировать его в реестре
- адаптеров, что бы его можно было запросить когда он понадобится. Здесь нет
- какого-либо глобального реестра адаптеров, таким образом мы должны
- самостоятельно создать для нашего примера реестр::
- >>> from zope.interface.adapter import AdapterRegistry
- >>> registry = AdapterRegistry()
- Реестр содержит отображение того, что адаптер реализует на основе другого
- интерфейса который предоставляет объект. Поэтому дальше мы регистрируем адаптер
- который адаптирует интерфейс `IFile` к интерфейсу `ISize`. Первый аргумент к
- методу `register()` реестра - это список адаптируемых интерфейсов. В нашем
- случае мы имеем только один адаптируемый интерфейс - `IFile`. Список
- интерфейсов имеет смысл для использования концепции мульти-адаптеров, которые
- требуют нескольких оригинальных объектов для адаптации к новому интерфейсу. В
- этой ситуации конструктор адаптера будет требовать новый аргумент для каждого
- оригинального интерфейса.
- Второй аргумент метода `register()` - это интерфейс который предоставляет
- адаптер, в нашем случае `ISize`. Третий аргумент - имя адаптера. Сейчас нам не
- важно имя адаптера и мы передаем его как пустую строку. Обычно имена полезны
- если используются адаптеры для одинакового набора интерфейсов, но в различных
- ситуациях. Последний аргумент - это класс адаптера::
- >>> registry.register([IFile], ISize, '', FileSize)
- Теперь мы можем использовать реестр для запроса адаптера::
- >>> registry.lookup1(IFile, ISize, '')
- <class '__main__.FileSize'>
- Попробуем более практичный пример. Создадим экземпляр `File` и создадим адаптер
- использующий запрос реестра. Затем мы увидим возвращает ли адаптер корректный
- размер при вызове `getSize()`::
- >>> file = File()
- >>> size = registry.lookup1(IFile, ISize, '')(file)
- >>> size.getSize()
- 7
- На самом деле это не очень практично, т.к. нам нужно самим передавать все
- аргументы методу запроса. Существует некоторый синтаксический леденец который
- позволяет нам получить экземпляр адаптера просто вызвав `ISize(file)`. Что бы
- использовать эту функциональность нам понадобится добавить наш реестр к списку
- adapter_hooks, который находится в модуле с адаптерами. Этот список хранит
- коллекцию вызываемых объектов которые вызываются автоматически когда вызывается
- IFoo(obj); их предназначение - найти адаптеры которые реализуют интерфейс для
- определенного экземпляра контекста.
- Необходимо реализовать свою собственную функцию для поиска адаптера; данный
- пример описывает одну из простейших функций для использования с реестром, но
- также можно реализовать поисковые функции которые, например, используют
- кэширование, или адаптеры сохраняемые в базе. Функция поиска должна принимать
- желаемый на выходе интерфейс (в нашем случае `ISize`) как первый аргумент и
- контекст для адаптации (`file`) как второй. Функция должна вернуть адаптер,
- т.е. экземпляр `FileSize`::
- >>> def hook(provided, object):
- ... adapter = registry.lookup1(zope.interface.providedBy(object),
- ... provided, '')
- ... return adapter(object)
- ...
- Теперь мы просто добавляем нашу функцию к списку `adapter_hooks`::
- >>> from zope.interface.interface import adapter_hooks
- >>> adapter_hooks.append(hook)
- Как только функция зарегистрирована мы можем использовать желаемый синтаксис::
- >>> size = ISize(file)
- >>> size.getSize()
- 7
- После нам нужно прибраться за собой, что бы другие получили чистый список
- `adaper_hooks` после нас::
- >>> adapter_hooks.remove(hook)
- Это все. Здесь намеренно отложена дискуссия об именованных и мульти-адаптерах,
- т.к. данный текст рассчитан как практическое и простое введение в интерфейсы и
- адаптеры Zope 3. Для более подробной информации имеет смысл прочитать
- `adapter.txt` из пакета `zope.interface`, что бы получить более формальное,
- справочное и полное трактование пакета. Внимание: многие жаловались, что
- `adapter.txt` приводит их мозг к расплавленному состоянию!
|