Различия между версиями 24 и 35 (по 11 версиям)
Версия 24 от 2010-06-09 15:06:43
Размер: 27055
Редактор: RostislavDzinko
Комментарий:
Версия 35 от 2010-06-11 11:41:55
Размер: 34637
Редактор: RostislavDzinko
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 166: Строка 166:
Но интерфейс '''!IAnnotable''' слишком общий, так как не определяет способа хранения аннотаций, и не должен предоставляться объектами напрямую. Никогда не следует предполагать, что один метод будет работать для всех возможных объектов.

!BlueBream также имеет интерфейс '''IAttributeAnnotatable''', который позволяет хранить аннотации в атрибуте '''__annotations__'''. Это хранилище доступно для всех объектов, которые хранятся в ZODB.

С другой стороны имеется интерфейс '''!IAnnotations''', который предоставляет простой API (похожий на словари языка Python), который позволяет получать данные из аннотаций, используя уникальный ключ. Обычно этот интерфейс реализован как адаптер, который требует '''!IAnnotatable''', предоставляющий '''!IAnnotations'''. Таким образом, для реализации собственного механизма аннотаций необходимо реализовать интерфейс '''!IAnnotations'''.

Для '''!IAttributeAnnotable''' существует адаптер '''AttributeAnnotations'''. Заметьте, что по определению '''!IAnnotations''' расширяет '''!IAnnotable''', так как '''IAnnotation''' всегда может быть адаптирован сам к себе.
Но интерфейс '''IAnnotable''' слишком общий, так как не определяет способа хранения аннотаций, и не должен предоставляться объектами напрямую. Никогда не следует предполагать, что один метод будет работать для всех возможных объектов.

!BlueBream также имеет интерфейс '''IAttributeAnnotatable''', который позволяет хранить аннотации в атрибуте '''_ _annotations_ _'''. Это хранилище доступно для всех объектов, которые хранятся в ZODB.

С другой стороны имеется интерфейс '''!IAnnotations''', который предоставляет простой API (похожий на словари языка Python), который позволяет получать данные из аннотаций, используя уникальный ключ. Обычно этот интерфейс реализован как адаптер, который требует '''IAnnotatable''', предоставляющий '''!IAnnotations'''. Таким образом, для реализации собственного механизма аннотаций необходимо реализовать интерфейс '''IAnnotations'''.

Для '''IAttributeAnnotable''' существует адаптер '''!AttributeAnnotations'''. Заметьте, что по определению '''IAnnotations''' расширяет '''IAnnotable''', так как '''IAnnotation''' всегда может быть адаптирован сам к себе.
Строка 175: Строка 175:

=== Контент-провайдеры (Content provider) ===

Контент-провайдер (content provider) - термин, который пришел из мира Java, и относится к компонентам, которые предоставляют HTML содержимое. И ничего больше! Способ поиска и предоставления содержимого лежит полностью на плечах того, кто делает реализацию. В !BlueBream контент-провайдеры - это мульти-адаптеры, которые ищутся по контексту, запросу (request) (можно еще и по слою/скину), и виду, в котором они отображаются.

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

=== Вьюлеты (Viewlet) ===

Вьюлеты представляют собой базовый фреймворк для построения подключаемых (pluggable) пользовательских интерфейсов.

== Технологии ==

=== ZODB ===

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

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

Хранение объектов в реляционных базах данных требует поступится свободой выбора формы хранения данных. Ограничения реляционной модели чинят препятствия написанию объектно-ориентированного кода.

ZODB - это чисто объектная база данных, которая хранит объекты разрешая работать с объектами в пределах всех парадигм, которые могут быть выражены языком Python. Вот почему программный код становится проще, надежнее, и легче для понимания.

Также важно то, что между базой данных и программой нет никакой прослойки и отображений (mappings). Просмотрите учебник, чтобы увидеть, как все просто.

Вот несколько особенностей ZODB:

 * прозрачная персистентность Python объектов;
 * полностью ACID-совместимая поддержка транзакций (включая точки сохранения);
 * история, возможность делать откаты;
 * эффективная поддержка больших двоичных объектов (BLOB);
 * подключаемые хранилища;
 * масштабируемая архитектура.

=== ZCML ===

!BlueBream отделяет все политики от кода и выреносит ее в конфигурационные файлы. Язык разметки конфигурации (ZCML), XML-основанный язык конфигурцаии, который используется для этих целей, ограничен тем, что, по большому счету, выполняет регистрацию компонентов и настройки безопасности. Включая или выключая определенные компоненты в ZCML, вы можете настраивать определенные политики для всего приложения. Если вы это не сделаете явно, то компонент не будет найден.

=== WSGI ===

[[http://wsgi.org/wsgi/|WSGI]] - это интерфейс шлюза веб серверов (Web Server Gateway Interface). Этоспецификация для веб серверов и серверов приложений, описывающая способ общенния с веб приложениями. Она является стандартом языка Python, описанным детально в [[http://www.python.org/dev/peps/pep-0333/|PEP 333]].

=== PaseScript ===

!PasteScript - это внешний пакет, созданный Яном Биклингом (Ian Bicking).

!PasteScript - это фреймворк для описания команд. Он поставляется с набором команд из коробки, среди которых ''paster serve'' и ''paster create''.

Команда '''paster serve''' загружает и обслуживает WSGI приложение, определенное в файле настроек Paste Deploy. Команда '''paster create''' command создает дерево пакетов по шаблону.

=== PasteDeploy ===

!PasteDeploy - внешний пакет, созданный Яном Биклингом (Ian Bicking).

!PasteDeploy - это система для загрузки и настройки WSGI приложений и серверов. !PasteDeploy создает WSGI приложение как описано в конфигурационном файле. INI формат конфигурационного файла определен в !PasteDeploy.

Из сайта !PasteDeploy:

Paste Deployment - это система для поиска и настройки WSGI приложений и серверов. Пользователям WSGI приложения система предоставляет единственную простую функцию (loadapp) для загрузки приложения из конфигурационного файла или яйца (Python Egg). От провайдеров WSGI приложений она требует только единственной точки входа в приложение, так что пользователи приложения не должны вникать в реализацию вашего приложения.

'''Перевод: Ростислав Дзинько'''

Конценпции и технологии

Концепции

Интерфейсы (Interface)

Интерфейсы - это объекты, описывающие внешнее поведение других объектов, которые предоставляют (provide) реализуемые ими интерфейсы (своего рода спецификация). Интерфейс предоставляет спецификацию объектов, которые их реализуют через:

  • информационную документация в строках документации (doc string);
  • определение атрибутов;
  • инварианты (Invariants), то есть условия, которые должны выполнятся для объектов, которые предоставляют (provide) интерфейс;

Вот несколько преимуществ, которые вы получаете при использовании интерфейсов:

  • избегаете монолитной структуры приложения, разрабатывая маленькие кусочки для повторного использования;
  • моделируете внешнюю функциональность и поведение;
  • Устанавливаете связи между отдельными частями приложения;
  • документируете API

Компонентная архитектура Zope

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

Компонентная архитектура Zope - способ создания компонентов многоразового использования, а не набор этих компонентов.

Компонент - это объект, предназначенный для многоразового использования обладающий обозримым (introspectable )интерфейсом. С другой стороны компоненты являются связуемыми расцепленными объектами. Компонент предоставляет (provides) интерфейс, который реализован классом. При этом не имеет значения, каким образом реализован компонент, важно то, что он соответствует интерфейсу, который описывает его поведение и функциональность. Интерфейс - это объект, который описывает поведение конкретного компонента. Используя компонентную архитектуру Zope, можно избавиться от сложности разрабатываемой системы, распылив ее на мелкие взаимодействующие между собой компоненты. Компонентная архитектура Zope помогает создавать два основных типа компонентов - адаптеры и утилиты.

События (Event)

События - это объекты, которые сигналят о том, что в системе что-то случилось. Они используются для расширения обработки запросов путем обеспечения точек вставки реакции на них (plug points). Пакет zope.event обеспечивает базовую систему публикации событий. Также этот пакет предоставляет очень простую диспетчерскую систему, на основе которой можно построить более сложные. Например, в пакете zope.component находится диспетчерская система основанная на типах, и построена она на основе пакета zope.event.

Адаптеры (Adapter)

Адаптеры используют интерфейс существующего компонента и адаптируют этот компонент, наделяя возможностью предоставлять другой интерфейс.

Когда приложения вырастают, в коде возникает побочный эффект, так называемый макаронный эффект: взаимодействия между классами могут привести к нежелаемым зависимостям и код превращается в монолитный блок.

Адаптеры предоставляют возможность избежать этого, реализуя принцип подстановки Лисков.

Адаптеры предоставляют механизм сотрудничества между данным объектом и конкретным контекстом посредством интерфейсов. Они позволяют произвольному классу быть совместимым с данным интерфейсом, предоставляя слой совместимости (compatibility layer).

Этот механизм используется и в других системах, например, Microsoft COM QueryAdapter, и позволяет разработчику собирать объекты в специфический функционирующий контекст. Другое название этого механизма - склеивающий кода (glue code).

Использование адаптеров предоставляет следующие преимущества:

  • Они позволяют собирать экземпляры классов в контексты, для которых эти классы не были реализованы, без изменения программного кода исходных классов, а также не добавляя к ним зависимостей.
  • Они предоставляют легкий путь сборки общих функций, которые могут применяться к различным классам.
  • С другой стороны адаптеры - формализованный путь утиной типизации (duck typing), который был предложен несколько лет назад в рамках PEP 246. Есть и другие реализации адаптеров, например PyProtocols.

Утилиты (Utility)

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

Утилиты в большинстве случаев используются для создания простых компонентов, которые выполняют одно простое задание, например, синтаксический анализатор XML. Иногда очень полезно зарегистрировать объект, который ничего не адаптирует. Примерами таких объектов могут служить: соединение к базе данных, синтаксический анализатор XML, объект, генерирующий уникальные идентификаторы и т.д. Этот тип компонентов предоставляется ZCA и называется вспомогательными компонентами (utility components).

Утилиты - простые объекты, поиск которых производится по интерфейсу и имени. Такой подход делает необходимым создание глобального реестра, в котором можно зарегистрировать экземпляры утилит с тем, чтобы они были доступны из любой точки приложения, тем самым избавляя разработчика от потребности повсеместно передавать экземпляры классов утилит в качестве параметров выполняемой задачи.

Подписчики (Subscriber)

В отличии от обычных адаптеров, адаптеры-подписчики (subscription adapters, subscribers) используются тогда, когда нужны сразу все адаптеры, которые адаптируют объект к определенному интерфейсу. Адаптеры-подписчики также известны как просто подписчики.

Обработчики (Handler)

Обработчики - это фабрики подписчиков, которые ничего не производят. Они просто выполняют свою работу в тот момент, когда их вызвали. Обработчики обычно используются для оказания реакции на события. Обработчики также называют подписчиками на события (event subscribers) или адаптерами-подписчиками на события (event subscription adapters).

Подписчики на события (Event subscribers) отличаются от других подписчиков тем, что тот, кто их вызывает не ожидает от них прямого взаимодействия. Например, от публикатора событий (event publisher) не ожидается никакого возвращаемого значения. Рекомендуется оформлять подписчики в виде функций, а не классов, из-за того, что подписчики не предоставляют программного интерфейса (API) тем, кто их вызывает.

Реестры компонентов

Реестры хранят список доступных компонентов, информацию о том, какие интерфейсы они предоставляют, какой интерфейс(ы) они, возможно, адаптируют, а также необязательное регистрационное имя. Пакет zope.component реализует глобальный реестр компонентов. Пакет zope.site предоставляет локальный персистентный реестр компонентов, который называется сайт-менеджер (site manager), и предназначен для регистрации локальных утилит и адаптеров.

Публикация объектов

BlueBream выкладывает объекты в веб. Этот процесс называется публикацией объектов. Одна из уникальных характеристик BlueBream - это способ, который предлагается для перехода от объекта к объекту, и вызова методов этих объектов только с помощью URL. Вдобавок к HTTP, BlueBream предоставляет возможность публикации путем использования и других протоколов как FTP, WebDAV и XML-RPC.

Виды (view)

Виды обеспевивают связь между внешним воздействием и объектом.

Вид обычно является компонентом отображения, и в большинстве случаев является ответственным за создание HTML-кода. Виды могут возвращать HTML напрямую, но часто также содержат логику отображения и обрабатывают данные для передачи в шаблон страницы (Zope Page Template), который уже содержит HTML.

Веб-разработчики обычно имеют дело со специальным типом видов, который именуется видом обозревателя (BrowserView). Это обычный вид (View), созданный для веб-браузера, но BlueBream также предоставляет виды для других протоколов, таких как FTP или WebDAV. В виде обозревателя, внешним воздействием является запрос (request), а объект, к которому будет применен вид ищется путем траверсинга (traversal) и называется контекстом. Большинство Zope разработчиков, говоря вид имеют в виду именно вид обозревателя, поскольку веб - наиболее часто применяемая среда для публикации объектов.

Констурктор браузерного вида (BrowserView) выглядит следующим образом:

   1 class BrowserView(Location):
   2     implements(IBrowserView)
   3 
   4     def __init__(self, context, request):
   5         self.context = context
   6         self.request = request

Контекст - это объект, на который воздействует вид. Часто контекст является контент-объектом, и это может быть даже объект-контейнер (Container),объект-сайт (Site), или любой другой объект, который Zope может опубликовать.

Запрос (Request) - это HTTP запрос. Если вид является видом обозревателя, запрос будет содержать аттрибут form, который хранит все данные форм, которые доступны программисту в подготовленном виде.

Представим себе следующий адрес: http://localhost:8080/your-id/a-todo-list/get-cat-food. В BlueBream, your-id - это контейнерный компонент, который также предоставляет интерфейс !IHomeFolder, a-todo-list - контейнер задач, который также предоставляет интерфейс (!IToDoList), а get-cat-food - это компонент, что предстваляет единицу содержимого, а также предоставляет интерфейс (!IToDoItem). Если ввести URL http://localhost:8080/your-id/a-todo-list/get-cat-food в браузере, то контекстом будет объект, предоставляющий интерфейс !IToDoItem, в то время как запросом будет объект, который отображает HTTP-запрос. Но, если URL такой: http://localhost:8080/your-id/, то контекстом является ваша домашняя папка.

Вы можете сделать просмотр вида программным путем с помощью запроса (query):

   1 view = component.queryMultiAdapter((object, request), name='index')

Для получения более подробной информации о видах, обратитесь к разделу о них в Plone Core Developer Reference. Этот справочный материал предоставляет информацию о том, как виды BlueBream используются в Plone: http://plone.org/documentation/manual/plone-developer-reference/patterns/views.

Контент-объекты (Content object)

Контент-объекты, или объекты содержимого - это объекты, которые имеют виды, видимые пользователем.

Если интерфейс предоставляет тип интерфейса zope.app.content.interfaces.IContentType, то все объекты, которые предоставляют этот интерфейс считаются контент-объектами.

Контейнеры

Контейнеры - это контент-объекты, которые являются вместилищем для других контент-объектов.

Схемы (Schema)

Cхемы - это расширение интерфейсов, поэтому зависят от пакета zope.interface. Поля в схемах эквивалентны методам в интерфейсах. Вместе они дополняют друг друга, так как описывают разные стороны объекта. Методы интерфейса описывают функции, которые будет выполнять компонент, тогда как поля схемы отражают состояние.

Схемы обеспечивают:

  • полную спецификацию свойств на уровне API;
  • валидацию и конверсию вводимых данных;
  • автоматизированное создание форм пользовательского интерфейса (в основном в веб-обозревателе).

Виджеты (Widget)

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

Виджеты делятся на две группы: виджеты отображения и ввода. Виджеты отображения (Display widgets) зачастую являются довольно простыми и показывают только текстовое представления объекта Python. Напротив, виджеты ввода (input widgets), являются более сложными.

Cлои (Layer)

  • Определяют "поведение" (“feel”) сайта;
  • Содержат логику представления;
  • Выражаются в: страницах, контент-провайдерах, менеджерах вьюлетов (viewlet managers), и вьюлетах (viewlets);
  • Создаются разработчиками BlueBream приложений;

Скины (Skin)

  • определяют внешний вид сайта (“look”);
  • выражаются в шаблонах и ресурсах (CSS, Javascript, другое.);
  • используют слои для получения данных в шаблонах;
  • разрабатываются дизайнерами, верстальщиками и скриптовальщиками.

Технически скины - это интерфейсы, которые наследуются от специального интерфейса с именем !IDefaultBrowserLayer. !IDefaultBrowserLayer определен в модуле zope.publisher.interfaces.browser. Вы также можете наследоваться от уже существующего скина. Также важной является регистрация интерфейса скина с указанием типа !IBrowserSkinType. Скины напрямую предоставляются запросами (request).

Слои и скины

  • реализованы как интерфейсы;
  • BlueBream не делает между ними разницы;

  • разница между ними в том, что слои определяют поведение (“feel”), а скины внешний вид (“look”) приложение. Вы можете не придерживаться такого соглашения, если для вас оно слишком абстрактно. Но если вы разрабатываете приложение, содержащее множество различных отображений, настоятельно рекомендуется следовать такому соглашению, поскольку, по сути, оно соответствует идеологии разделения задач.
  • поддерживают наследование/заимствование;

Аннотации (Annotation)

Каждый объект, который обитает в среде BlueBream, и может быть аннотирован, использует атрибутные аннотации. Атрибутные аннотации (Attribute annotations) хранятся прямо в объектах. Такая реализация прекрасно работает тогда, когда объект является персистентным и хранится в ZODB. Но если вы используете объекты, хранящиеся в SQL-ных базах данных, например, посредством ORM, атрибутные аннотации работать не будут. В таких случаях единственный выход - собственная реализация механизма аннотаций.

Для работы с аннотациями существует интерфейс !IAnnotatable. Предоставляя этот интерфейс, объект заявляет, что он умеет хранить ждя себя аннотации.

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

BlueBream также имеет интерфейс IAttributeAnnotatable, который позволяет хранить аннотации в атрибуте _ _annotations_ _. Это хранилище доступно для всех объектов, которые хранятся в ZODB.

С другой стороны имеется интерфейс !IAnnotations, который предоставляет простой API (похожий на словари языка Python), который позволяет получать данные из аннотаций, используя уникальный ключ. Обычно этот интерфейс реализован как адаптер, который требует IAnnotatable, предоставляющий !IAnnotations. Таким образом, для реализации собственного механизма аннотаций необходимо реализовать интерфейс IAnnotations.

Для IAttributeAnnotable существует адаптер AttributeAnnotations. Заметьте, что по определению IAnnotations расширяет IAnnotable, так как IAnnotation всегда может быть адаптирован сам к себе.

Второй важный аспект аннотаций - это ключ (уникальный идентификатор), который используется в отображении (mapping). Так как аннотации могут содержать большое количество данных, важно выбирать ключи так, чтобы они всегда были уникальными. Самый простой способ убедится в уникальности - добавить имя пакета в ключ. По такому принципу, например, работают аннотации dublin core. Они, вместо того, чтобы использовать ZopeDublinCore, в качестве ключа используют zope.app.dublincore.ZopeDublinCore. Некоторые разработчики также используют соглашение пространств имен, основанное на URI: http://namespace.zope.org/dublincore/ZopeDublinCore/1.0.

Контент-провайдеры (Content provider)

Контент-провайдер (content provider) - термин, который пришел из мира Java, и относится к компонентам, которые предоставляют HTML содержимое. И ничего больше! Способ поиска и предоставления содержимого лежит полностью на плечах того, кто делает реализацию. В BlueBream контент-провайдеры - это мульти-адаптеры, которые ищутся по контексту, запросу (request) (можно еще и по слою/скину), и виду, в котором они отображаются.

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

Вьюлеты (Viewlet)

Вьюлеты представляют собой базовый фреймворк для построения подключаемых (pluggable) пользовательских интерфейсов.

Технологии

ZODB

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

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

Хранение объектов в реляционных базах данных требует поступится свободой выбора формы хранения данных. Ограничения реляционной модели чинят препятствия написанию объектно-ориентированного кода.

ZODB - это чисто объектная база данных, которая хранит объекты разрешая работать с объектами в пределах всех парадигм, которые могут быть выражены языком Python. Вот почему программный код становится проще, надежнее, и легче для понимания.

Также важно то, что между базой данных и программой нет никакой прослойки и отображений (mappings). Просмотрите учебник, чтобы увидеть, как все просто.

Вот несколько особенностей ZODB:

  • прозрачная персистентность Python объектов;
  • полностью ACID-совместимая поддержка транзакций (включая точки сохранения);
  • история, возможность делать откаты;
  • эффективная поддержка больших двоичных объектов (BLOB);
  • подключаемые хранилища;
  • масштабируемая архитектура.

ZCML

BlueBream отделяет все политики от кода и выреносит ее в конфигурационные файлы. Язык разметки конфигурации (ZCML), XML-основанный язык конфигурцаии, который используется для этих целей, ограничен тем, что, по большому счету, выполняет регистрацию компонентов и настройки безопасности. Включая или выключая определенные компоненты в ZCML, вы можете настраивать определенные политики для всего приложения. Если вы это не сделаете явно, то компонент не будет найден.

WSGI

WSGI - это интерфейс шлюза веб серверов (Web Server Gateway Interface). Этоспецификация для веб серверов и серверов приложений, описывающая способ общенния с веб приложениями. Она является стандартом языка Python, описанным детально в PEP 333.

PaseScript

PasteScript - это внешний пакет, созданный Яном Биклингом (Ian Bicking).

PasteScript - это фреймворк для описания команд. Он поставляется с набором команд из коробки, среди которых paster serve и paster create.

Команда paster serve загружает и обслуживает WSGI приложение, определенное в файле настроек Paste Deploy. Команда paster create command создает дерево пакетов по шаблону.

PasteDeploy

PasteDeploy - внешний пакет, созданный Яном Биклингом (Ian Bicking).

PasteDeploy - это система для загрузки и настройки WSGI приложений и серверов. PasteDeploy создает WSGI приложение как описано в конфигурационном файле. INI формат конфигурационного файла определен в PasteDeploy.

Из сайта PasteDeploy:

Paste Deployment - это система для поиска и настройки WSGI приложений и серверов. Пользователям WSGI приложения система предоставляет единственную простую функцию (loadapp) для загрузки приложения из конфигурационного файла или яйца (Python Egg). От провайдеров WSGI приложений она требует только единственной точки входа в приложение, так что пользователи приложения не должны вникать в реализацию вашего приложения.

Перевод: Ростислав Дзинько

Документации/Bluebream/Bluebream-Концепции-Технологии (последним исправлял пользователь RostislavDzinko 2010-06-11 11:41:55)