Interface

Zope-Interfaces sind Objekte, die das externe Verhalten desjenigen Objekts spezifizieren, das sie bereitstellt. Dies geschieht durch:

  • Informelle Dokumentationen in Doc-Strings.
  • Attribut-Definitionen
  • Invarianten, also Bedingungen für Objekte, die dieses Interface bereitstellen.

Dabei spezifiziert ein Interface die Charakteristiken eines Objekts, sein Verhalten und seine Fähigkeiten.

Interfaces machen Angaben, was ein Objekt bereitstellt, nicht wie es bereitgestellt wird. Sie beruhen auf dem Design By Contract-Modell.

Während in einigen anderen Programmiersprachen Interfaces ein Bestandteil der Sprache selbst sind, werden in Python mit der ZCA Interfaces als Meta-Klasse implementiert, die ererbt werden kann.

Interfaces erstellen

Für eine Komponente wird zunächst dessen Interface erstellt. Interface- Objekte werden üblicherweise mit Python Class Statements erstellt, sind jedoch selbst keine Klassen sondern Objekte. Ein Interface-Objekt wird nun als Subclass von zope.interface.Interface erstellt:

from zope.interface import Interface

class IHello(Interface):

    def hello(name):
        """Say hello to somebody"""

Durch das Subclassing von zope.interface.Interface wird nun das Interface-Objekt IHello erstellt:

>>> IHello
<InterfaceClass __main__.IHello>

Marker-Interfaces

Interfaces können auch verwendet werden um ein bestimmtes Objekt zu einem spezifischen Typ gehört. Ein solches Interface ohne Attribute und Methoden wird Marker-Interface genannt. Ein solches Interface kann z.B. so aussehen:

>>> from zope.interface import Interface

>>> class ISpecialGreeting(Interface):
...     """A special greeting"""

Invarianten

Gelegentlich sind Regeln mit einem oder mehreren Attributen für das Interface einer Komponente erforderlich. Solche Regeln werden Invarianten genannt und können mit zope.interface.invariant erstellt werden.

So kann z.B. für ein person-Objekt mit den Attributen name, email und phone ein Validator erstellt werden, der überprüft ob entweder email und phone angegeben wurden.

  1. Zunächst wird nun ein aufrufbares Objekt entweder als Funktion oder Instanz erstellt:

    >>> def contacts_invariant(obj):
    ...
    ...     if not (obj.email or obj.phone):
    ...         raise Exception(
    ...             "At least one contact info is required")
    

#. Anschließend wird das Interface des person-Objekts mit der zope.interface.invariant-Funktion definiert:

>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import invariant

>>> class IPerson(Interface):
...
...     name = Attribute("Name")
...     email = Attribute("Email Address")
...     phone = Attribute("Phone Number")
...
...     invariant(contacts_invariant)
  1. Schließlich kann die validateInvariants-Methode verwendet werden:

    >>> from zope.interface import implements
    
    >>> class Person(object):
    ...     implements(IPerson)
    ...
    ...     name = None
    ...     email = None
    ...     phone = None
    
    >>> veit = Person()
    >>> veit.email = u"veit@example.org"
    >>> IPerson.validateInvariants(veit)
    >>> chris = Person()
    >>> IPerson.validateInvariants(jill)
    Traceback (most recent call last):
    …
    Exception: At least one contact info is required
    

Interfaces implementieren

Dieses Hello-Interface kann nun assoziiert werden mit einer konkreten Klasse, in der das Verhalten definiert wird. In unserem Beispiel:

class HelloComponent:

    implements(IHello)

    def hello(self, name):
        return "Hello %s!" % name

Die neue Klasse HelloComponent implementiert das Hello-Interface.

Dabei kann eine solche Klasse auch mehrere Interfaces implementieren. Sollen also Instanzen unserer HelloComponent zusätzlich ein Other-Interface implementieren, wird einfach eine Sequenz der Interface-Objekte in der HelloComponent-Klasse bereitgestellt:

class HelloComponent:

    implements(IHello, IOther)
    …

Überprüfen der Implementierung

Mit implementedBy kann ein Interface gefragt werden, ob eine bestimmte Klasse oder Instanz dieses Interface implementiert. So sollte z.B. die Überprüfung, ob eine Instanz der HelloComponent-Klasse Hello``implementiert den Wert ``true zurückliefern:

IHello.implementedBy(HelloComponent)

Interfaces erweitern

Interfaces können einfach erweitert werden, so kann z.B. unser IHello-Interface um eine Methode lastGreeted erweitert werden:

class ISmartHello(IHello):
    """A Hello object that remembers who is greeted"""

    def lastGreeted(self):
        """Returns the name of the last person greeted."""
getBases
gibt eine Liste der Interfaces aus, die durch dieses Interface erweitert wurden, z.B.:
>>> ISmartHello.getBases()
(<InterfaceClass __main__.IHello>,)
extends
gibt true oder false`, je nachdem, ob ein Interface ein anderes erweitert oder nicht, z.B.:
>>> ISmartHello.extends(IHello)
True
>>> IOther(Interface):
...     pass
>>> ISmartHello.extends(IOther)
False

Interfaces abfragen (querying)

names
gibt eine Liste der Namen aller Items aus, die durch das Interface beschrieben werden, z.B.:
>>> IUser.names()
['getUserName', 'getPassword']
namesAndDescriptions
gibt eine Liste von Tuples (name, description) aus, z.B.:
>>> IUser.namesAndDescriptions()
[('getUserName', <zope.interface.interface.Method.Method object at 80f38f0>),
('getPassword', <zope.interface.interface.Method.Method object at 80fded8>)]

Marker interfaces