Версия 8 от 2010-07-09 12:49:58

Убрать это сообщение

Руководство по BlueBream (часть 2)

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

Введение

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

Вот несколько мотиваций для использования интерфейсов:

Классическая книга по программной инженерии Design Patterns, авторами которой являются Банда четырех рекомендует программировать на интерфейс, а не на реализацию. Определение формального интерфейса помогает в понимании системы. Более того, интерфейсы приносят все преимущества компонентной архитектуры Zope.

В некоторых современных языках программирования: Java, C#, VB.NET и т.д., интерфейсы - явный аспект языка. Поскольку в Python интерфейсов нет, Zope реализует их через механизм мета-классов, от которых необходимо наследоваться.

Я могу сделать X

У меня есть X

Вы можете сделать X используя меня

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

Определение интерфейсов

В Java, например, интерфейсы - специальные типы объектов, которые могут служить в качестве интерфейсов в их ограниченной области видимости.

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

Пример

Вот классический пример в стиле hello world:

   1 >>> class Host(object):
   2 ...
   3 ...     def goodmorning(self, name):
   4 ...         """Say good morning to guests"""
   5 ...
   6 ...         return "Good morning, %s!" % name

В этом классе вы определили метод goodmorning. Если вы вызываете этот метод из экземпляра этого класса, он вернет Good morning, ...!.

   1 >>> host = Host()
   2 >>> host.goodmorning('Jack')
   3 'Good morning, Jack!'

Здесь host - экземпляр класса (объект), который использует код приведенный выше. Если вам нужны детали реализации, вам следует обратится к классу Host, либо через исходный код, либо через API документации.

Теперь будем использовать интерфейсы. Для данного класса определим интерфейс следующим образом:

   1 >>> from zope.interface import Interface
   2 
   3 >>> class IHost(Interface):
   4 ...
   5 ...     def goodmorning(guest):
   6 ...         """Say good morning to guest"""

Как видите, интерфейс является потомком класса zope.interface.Interface. Префикс I - очень полезная конвенция для именования интерфейсов.

Объявление интерфейсов

В предыдущей главе Вы уже увидели как объявлять интерфейс с использованием пакета zope.interface. Эта глава объясняет технологию интерфейсов более подробно.

Возьмем в качестве примера следующее:

   1 >>> from zope.interface import Interface
   2 >>> from zope.interface import Attribute
   3 
   4 >>> class IHost(Interface):
   5 ...     """A host object"""
   6 ...
   7 ...     name = Attribute("""Name of host""")
   8 ...
   9 ...     def goodmorning(guest):
  10 ...         """Say good morning to guest"""

Интерфейс IHost обладает двумя атрибутами: name и goodmorning. Вспомните что, по крайней мере в Python, методы также являются атрибутами классов. Атрибут name определен с использованием класса zope.interface.Attribute. Когда вы добавляете атрибуту name к интерфейсу IHost, то не устанавливаете начальное значение. Здесь цель определения этого атрибута - просто показать, что любая реализация этого интерфейса будет содержать атрибут name. В этом случае, мы даже не указали тип атрибута! Вы можете передать строку документации в качестве первого параметра конструктору Attribute.

ругой атрибут, goodmorning - это метод, который определен с использованием синтаксиса функций. Заметьте, что self в этом случае не обязателен, потому что детали реализации - это уже класс. Например, модуль может реализовать интерфейс. Если модуль реализует этот интерфейс, он будет обладать атрибутом name и содержать определенную функцию goodmorning. Функция goodmorning будет принимать один аргумент.

Сейчас мы осветим связку интерфейс-класс-объект. Объект - это "реальная живая" вещь, и объекты являются экземплярами классом. Интерфейс - действительное формальное описание объекта, соответственно классы - просто реализация интерфейса. Вот почему следует программировать интерфейсы, а не реализации.

Теперь вам следует ознакомится еще с двумя понятиями объединяющими эти концепции. Первое - предоставлять (provide), а второе - реализовать (implement). Объекты предоставляют интерфейсы, а классы реализуют интерфейсы. Другими словами, объекты предоставляют интерфейсы, которые реализуют их классы. В предыдущем примере host (объект) предоставляет IHost (интерфейс), а Host (класс) реализует IHost (интерфейс). Один объект может предоставлять более одного интерфейса; также один класс может реализовать более одного интерфейса. Объекты также могут предоставлять интерфейсы прямо, вдобавок к тому, что реализуют их классы.

Классы - реализация объектов. В Python, классы - вызываемые объекты, так почему бы другим вызываемым объектам не реализовать интерфейс? Да, это возможно. Вы можете указать любому вызываемому объекту, что он создает объекты, предоставляющие некие интерфейсы, говоря что этот вызываемый объект реализует интерфейсы. Вызываемые объекты в общем случае называются фабриками (factories). Так как функции - вызываемые объекты, они могут реализовать интерфейсы.

Реализация интерфейсов

Для того, чтобы объявить о том, что класс реализует некий интерфейс, используйте функцию zope.interface.implements в коде определения класса.

Представьте себе пример, в котором Host реализует IHost:

   1 >>> from zope.interface import implements
   2 
   3 >>> class Host(object):
   4 ...
   5 ...     implements(IHost)
   6 ...
   7 ...     name = u''
   8 ...
   9 ...     def goodmorning(self, guest):
  10 ...         """Say good morning to guest"""
  11 ...
  12 ...         return "Good morning, %s!" % guest

Если вам интересно, как работает функция implements, обратитесь к блогу James Henstridge (http://blogs.gnome.org/jamesh/2005/09/08/python-class-advisors/). В разделе адаптеры, вы найдете функцию adapts, которая работает таким же образом.

Так как Host реализует IHost, экземпляры класса Host предоставляют IHost. Существуют также вспомогательные методы для просмотра деклараций. Декларация также может быть описана вне класса. Если вы не сделали interface.implements(IHost), как показано в примере выше, то после определения класса, вы можете сделать следующее:

   1 >>> from zope.interface import classImplements
   2 >>> classImplements(Host, IHost)

Интерфейсы-маркеры

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

Вот пример интерфейса-маркера:

   1 >>> from zope.interface import Interface
   2 
   3 >>> class ISpecialGuest(Interface):
   4 ...     """A special guest"""

Этот интерфейс может использоваться для того, чтобы объявить о том, что объект является специальным гостем.

Контент компоненты (Content component)

Введение

Взгляните на следующий пример:

   1 >>> from zope import interface
   2 
   3 >>> class IPerson(interface.Interface):
   4 ...     name = interface.Attribute("Name")
   5 >>> class Person(object):
   6 ...     interface.implements(IPerson)
   7 ...     name = None
   8 >>> jack = Person()
   9 >>> jack.name = "Jack"

В этом примере jack - это контент-компонент. Следовательно, контент-компонент - не что иное, как объект, который предоставляет определенный интерфейс. Как было сказано в предыдущей главе, можно использовать zope.schema для определения полей интерфейса. В предыдущем интерфейсе, их можно определить примерно так:

   1 >>> from zope import interface
   2 >>> from zope import schema
   3 
   4 >>> class IPerson(interface.Interface):
   5 ...     name = schema.TextLine(
   6 ...         title=u"Name",
   7 ...         description=u"Name of person",
   8 ...         default=u"",
   9 ...         required=True)

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

Сначала рассмотрим желание клиента, которое будет реализовано в следующих главах.

  1. Один контейнер заявок для каждого проекта. Много контейнеров могут быть добавлены в проект.
  2. В контейнер может быть добавлено любое количество заявок.
  3. Каждая заявка будет добавлена с описанием и одним начальным комментарием.
  4. Следующие комментарии могут быть добавлены к заявкам.
  5. Эта глава начинает простую реализацию сборщика заявок.

Как говорилось выше, наша цель - разработать полностью рабочий, хотя необязательно красиво выглядящий, веб ориентированный сборщик заявок. Корневым объектов будет Collector, который может содержать объекты заявок (Ticket) от различных пользователей. Так как вы хотите разрешить пользователям отвечать на заявки, вам следует предоставить заявкам возможность хранить в себе ответы, которые и есть объектами Comment.

Это значить, что у вас есть два компонента контейнера: Collector содержит только заявки, и может быть добавлен в любую папку Folder или другой контейнер, который способен содержать его. Для того, чтобы сделать сборщик заявок поинтереснее, он также должен иметь описание, которое вкратце представляет предмет/тему дискуссии. Заявки, с другой стороны могут хранится только в объекте Collector. Каждая из них будет иметь краткое и развернутое описание. И напоследок, Comment должен хранится только в заявках.

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

Наиболее удобное место для расположения пакета - $HOME/myzope/lib/python. Для создания пакета, добавьте папку:

   1 $ cd $HOME/myzope/lib/python/
   2 $ mkdir collector

(на GNU/Linux).

Для того, чтобы сделать эту папку пакетом, добавьте туда пустой файл init.py. В GNU/Linux вы можете сделать примерно следующее:

   1 $ echo "# Make it a Python package" >> collector/__init__.py

но вы также можете использовать текстовый редактор и сохранить файл под этим именем. Просто удостоверьтесь, что в этом файле содержится правильный Python код. Файл должен содержать по крайней мере пробелы, так как пустые файлы плохо обрабатываются архиваторами.

Теперь вы будете работать только внутри пакета collector, который размещен в $HOME/myzope/lib/python/collector.

Схемы и виджеты

Введение

На ранних этапах разработки, разработчики BlueBream (раньше Zope 3) решили, что писать HTML формы вручную и вручную проверять правильность введенных данных довольно громоздко. Команда BlueBream поняла, что если расширить интерфейсы, можно будет автоматически генерировать формы и выполнять проверку введенных данных. Эта глава предоставляет информацию и формально представляет пакеты zope.schema и zope.formlib.

Schema - это интерфейс, который определен с помощью специальных атрибутов, называемых "полями".

Схема разрабатывалась для достижения трех основных целей:

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