1f190833

ACQUISITION - заимствование вместо наследования


Acquisition - это механизм запроса значений переменных из текущего контекста. Переведем это слово как "заимствование" атрибутов; относительно адекватный перевод.

Заимствование значения атрибута происходит из контекста объекта. Контекстов бывает два - статический, возникающий в момент создания объекта, и динамический, возникающий во время вызова метода объекта на выполнение. Откуда именно происходит заимствование - этим управляет программист при создании или использовании компонента.

Контекст - это стек, в котором происходит поиск атрибута. Например, если есть контекст [object, sub2, myObject] (на вершине находится myObject), и myObject запросил значение атрибута color, то поиск будет происходить в глубину стека. Сначала атрибут с таким именем будет искаться в myObject, если его там нет - поиск перейдет к sub2, потом к object.

Статический контекст - это путь от корня ZODB (ZODB, не сайта!) к объекту в иерархии объектов. Динамический контекст - это путь (стек), возникающий во время обхода иерархии объектов компонентом ZPublisher при обращении к объекту через URL.

Например, если есть путь /root/object/subobject/myObject, то это и есть статический контекст (точнее, контекстом является стек объектов [root, object, subobject, myObject]).

Динамический контекст зависит от URL. Если произошло обращение к адресу http://www.server/root/object/subobject/myObject, то в этом случае динамический контекст совпадает со статическим. Но при обращении к http://www.server/root/english/object/subobject/myObject (где english - папка в объекте root) контекст будет другой - в стек добавится объект english. Чтобы понять, на какое именно место englsih добавится, надо подробно рассмотреть процесс траверса. ZPublisher сам тоже использует механизм acquisition, так что в целом разбор адреса http://www.server/root/english/object/subobject/myObject происходит следующим образом.

Получив (от ZServer'а) путь /root/english/object/subobject/myObject, ZPublisher начинает обходить отдельные части пути, строя по ходу стек.
Сначала стек пуст, затем к нему добавляется root ( поиск начинается от корня ZODB, и проверяется, что объект с таким именем есть в корне), затем ZPublisher обнаруживает english и запрашивает его (с учетом заимствования); объект обнаруживается в /root и попадает в стек, затем идет object, который заимствуется не из english, а из /root, затем нормальным путем идут subobject и myObject. В данном случае стек просто совпал с URL. Но если бы в english был свой object, он бы заимствовался бы оттуда, а не из /root. И если бы в этом object не было subobject, то subobject опять заимствовался бы из /root (ели он там есть). В результате мы имели бы контекст (стек) [/root, /root/english, /root/english/object, /root/subobject, myObject]. И если бы myObject запросил атрибут language, отсутствующий в /root/subobject, он получил бы его из /root/english/object, а не из /root/object! Таким образом, меняя порядок компонент в URL, программист может совершенно менять вид и содержание сайта, не дублируя при этом огромные куски кода или текста. Надо лишь произвести правильную факторизацию - разбить код и оформление на небольшие объекты, и строить контекст (он еще называется acquisition path - маршрут заимствования значений атрибутов). Рассмотрим подробный пример. Два основных объекта Zope - это классы DTML Document и DTML Method, включенные в дистрибутив Zope. Они предназначены для разного типа использования. DTML Document хранит содержание, текст; его путь - заимствование из статического контекста. DTML Method предназначен для активных действий, он заимствует значения из динамического контекста. Еще есть класс Folder - папка. В ней хранятся другие объекты. Пусть, скажем, Документ my.html начинается со стандартного заголовка, и заканчивается стандартным подвалом. На языке DTML это выражается как <dtml-var standard_html_header> и <dtml-var standard_html_footer>. Разместим эти объекты на небольшом абстрактном (то есть существующим только в наших головах) сайте. Пусть есть корень (корень в ZODB есть всегда), в нем несколько папок, скажем, Razdel1 и Razdel2, 2 Метода - header и footer, и в Razdel2 - наш my.html. / standard_html_header standard_html_footer Razdel1 Razdel2 my.html Итак, браузер обращается к http://www.server/Razdel2/my.html.


ZPublsiher строит контекст [/, /Razdel2, /Razdel2/my.html] и вызывает рендеринг my.html. Тот начинает рендерится, и в самом начале встречает <dtml-var standard_html_header>. Запрашивается значение заголовка. В my.html такого объекта нет, в Razdel2 нет, поиск переходит в корень - там такой есть. Он выполняется (рендерится), потом выполнение возвращается в my.html, потом footer - все. Возьмем и добавим в Razdel2 другой header: / standard_html_header standard_html_footer Razdel1 Razdel2 standard_html_header my.html И опять обратимся к http://www.server/Razdel2/my.html. Теперь my.html позаимствует другой header, и выглядеть будет по-другому! Добавим в корень новый раздел, с другими header/footer: / standard_html_header standard_html_footer Razdel1 Razdel2 standard_html_header my.html NewLook standard_html_header standard_html_header И обратимся к http://www.server/Razdel2/NewLook/my.html. Будет ли my.html использовать header из NewLook? Нет! my.html - DTML Document, и всегда использует статический контекст. Его acquisition path всегда [/, /Razdel2, /Razdel2/my.html]. Добавим в Razdel1 объект DTML Method index.html / standard_html_header standard_html_footer Razdel1 index.html Razdel2 standard_html_header my.html NewLook standard_html_header standard_html_header И обратимся к http://www.server/Razdel1/index.html. Поскольку это Метод, то будет использован динамический контекст, но в данном случае он совпадает со статическим. А вот при обращении к http://www.server/Razdel1/NewLook/index.html динамический контекст будет другой, и index.html позаимствует атрибуты из NewLook - и будет выглядеть по другому! Изменим сайт последний раз. Все удалим, / standard_html_header standard_html_footer Razdel1 index.html Razdel2 my.html и отредактируем header/footer, так чтобы они включали на сайте, скажем, левую колонку. Назовем ее left-column, и создадим ее в корне и в разделах: / standard_html_header standard_html_footer left-column Razdel1 index.html left-column Razdel2 my.html left-column Теперь при вызове http://www.server/Razdel1/index.html будет показываться одна колонка, http://www.server/Razdel2/my.html - другая.


А header при этом один на всех! Как header знает, какую колонку использовать? Очень просто - он участвует в поиске по acquisition path, по контексту (статическому или динамическому в зависимости от того, откуда его вызвали), не более того. Эти разные left-column даже не обязаны даже быть экземплярами одного класса. В корне это может быть DTML Method, а в Razdel2 - ZNavigator. Header'у все равно, кого рендерить, он вызывает left-column, ничего не зная об его типе и устройстве (опять объектно-ориентированное программирование). Еще один пример, ближе к реальной жизни с Zope, но менее подробный. Предварительное замечание: когда URL ссылается не на метод объекта, а на сам объект, у него вызывается метод index_html. Создадим маленький сайт. В корень поместим DTML Method index_html простого содержания: <dtml-var standard_html_header> <dtml-var content> <dtml-var standard_html_footer> и DTML Document content, хранящий собственно содержание раздела, вообще без заголовка/подвала. / standard_html_header standard_html_footer index_html content Razdel1 content Razdel2 content Обратимся к корню сайта: http://www.server/. Корневая папка вызовет свой index_html, который интерпретируется, подгрузит соответствующие заголовок и подвал. Ничего особенного. Теперь обратимся к одному из разделов: http://www.server/Razdel1/ Этот папка, поэтому она вызовет свой index_html... Но в Razdel1 нет своего index_html. Он заимствуется из корня! Ну, и поскольку он DTML Method, то он сам заимствует атрибуты из динамического контекста. Header/footer возьмутся из корня, а content из Razdel1. Третий, и последний пример совсем кратко. В папке db лежат dtml-методы (пусть dtml-методы будут называться db/view, db/insert, db/update), и sql-методы, которые параметризованы (имя таблицы, имена столбцов). Далее, внутри этой папки делается папка, например users. В ее атрибуты прописываются конкретные параметры для методов (имя таблицы, имена столбцов). По обращению db/users/view - получаем готовую страничку с содержимым таблицы.Метод view (равно как и insert и update) унаследован из db, но заимствует атрибуты из users. Метод db/users/insert (унаследованный из db) прочтет из свойств папки db/users название таблицы, названия полей, и сконструирует форму, для добавления записи. То же будет происходить с другими папками, и их свойствами. В ходе развития проекта, точно так-же как и для случая ОО программирования, добавление новых методов в "базовый объект" db (например нужно будет сделать поиск - db/search) автоматически расширит функциональность "потомков" db/users, db/something...

Содержание раздела