2008-01-11

Использование интерфейсов.txt

  2008-01-11 19:02

...

Использование интерфейсов:

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

Сразу разделим, как это сделано в определении компонентной модели, два понятия: интерфейс и схема интерфейса. Про схемы будет рассказано в статье Использование схем интерфейсов.txt.

Определение интерфейса:

Интерфейс это класс специального метакласса (или, по простому, специального вида). Он порождается от класса zope.interface.Interface примерно вот так:

        from zope.interface import Interface

        class IA(Interface) :
            """ Это просто тестовый интерфейс A"""

Обычно такие определения располагаются в файле interfaces.py вашего продукта.

Интерфейсы можно порождать друг от друга, и при этом при их использовании будут выполнятся правила наследования, правда, не всегда тривиальным способом (об этом позаботится как достаточно нетривиальная логика использования интерфейсов, так и кривая реализация поддержки этой логики):

        class IB(Interface) :
            """ Это просто тестовый интерфейс B """

        class IAB(IA,IB) :
            """ Это тестовый интерфейс от A и B """

В последнем случае компонент, предоставляющий интерфейс IAB, будет найден и при поиске компонента, предоставляющего IA, и при поиске компонента предоставляющего IB. Поскольку все интерфейсы наследуются от интерфейса Interface, во всех определениях Interface - синоним "любой интерфейс".

Интерфейс можно зарегистрировать директивой ZCML:

        <zope:interface
            interface=".interfaces.IA"
            name="qq"
            type="zope.app.content.interfaces.IContentType" />

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

Зарегистрированный интерфейс можно найти, используя обычный реестр утилит

Декларация интерфейса:

Для любого класса можно декларировать интерфейс. Обычно это делается таким образом:

        from zope.interface import implements
        from interfaces import IA

        class A(object) :
            implements(IA)

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

        zope.interface.classImplements(A,IA,IB,IC,...)

Либо, что чаще, директиву ZCML:

        <zope:class class="a.A">
            <implements interface="interfaces.IA">
        </zope:class>

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

Как узнать о том, что компонент (экземпляр класса) декларирует интерфейс:

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

        IA(a,None)

Про более сложные случаи рассказывалось во введении в компоненты.

Ну и два плохих способа:

        IA.providedBy(a)

        from zope.interface import providedBy

        IA in providedBy(a)

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

Заключение:

На этом использование интерфейса просто как декларации возможности взаимодействия заканчивается. И, на самом деле, этого действительно достаточно для очень многих задач. Однако рассказ об использовании схем интефейсов более обширен.

Официальный сайт Zope3 Московская группа изучения реактивного движения The Dream Bot Site noooxml