Interfaces Zope 3
Ce chapitre introduit le concept d'interface et son usage dans Zope 3.
Ce texte est une traduction libre d'une partie du livre "Zope Guide" écrit par une communauté de développeur Zope. L'originial
est disponible sur : http://kpug.zwiki.org/ZopeGuide. Conformément à la licence du texte original (je déteste la CC pour ça...), je dois citer l'auteur, qui est : "Zope 3 Distilled
Contributors (http://kpug.zwiki.org/ZopeGuideContributors)". Notons que malgrès l'attribution de la licence, ce texte reviens vraiment à Baiju M. qui en est le véritable auteur.
| Author: | Zope 3 Distilled Contributors (http://kpug.zwiki.org/ZopeGuideContributors) |
| Version: | 0.1.6 |
| Date: | 7 septembre 2006 |
| Copyright: | Creative Commons Attribution-ShareAlike 2.5 License |
| Traduction: | Benjamin Poulain < ikipou AT (@) gmail.com > |
Introduction
Le livre Design Patterns par la bande des quatres (GoF) est un classique de la littérature sur le génie logiciel. Dans ce livre, ils recommandent : "Programmez pour une interface, par pour une implémentation". Pour simuler la définition formelle d'une interface en C++, ils utilisèrent des classes composées de fonctions virutelle. De façon similaire, Zope utilise des classes hérité de la metaclasse zope.interface.Interface pour définir une interface. A la différence de langage comme Java, C#, etc, la notion d'interface ne fait pas partie de Python lui-même.
La base de l'orienté objet est la communication entre objets. La communication se fait à travers les messages. Les méthodes sont utilisé en Python pour manipuler les messages.
Regardez cette classe :
class Host(object)
def goodmorning(self, name):
"""Dit bonjour aux invités"""
return "Boujour %s!" % name
Dans cette classe, la méthode goodmorning est définit dans Host. Si un autre objet envoi un message goodmorning à un objet créé à l'aide de cette classe, celle-ci vas retourner "Bonjour ...!". Voici comment on envoie les messages (synonyme de appeller une méthode) :
>>> host = Host()
>>> host.goodmorning('Jack')
'Bonjour Jack!'
Ici host fait référence à l'objet instancié. Les détails d'implémentation de cette objet se trouvent dans la classe Host. Pour trouver comment cette objet se comporte (quel sont ses méhodes et attributs), on peut soit aller voir les détails de l'implémentation (Host), soit créer de la documentation.
Les spécifications d'un objet sont généralement nommé Application Programming Interface (API). Il y a de nombreux avantages à spécifier l'interface publique d'un objet.
Le package zope.interface est la base de Zope 3 pour définir des interfaces d'objets. Pour la classe introduite précédement, vous pouvez spécifier l'interface de cette façon :
from zope import interface
class IHost(interface.Interface)
def goodmorning(name):
"""Dit bonjour aux invités"""
Comme vous pouvez le constater la définition d'interface se fait à l'aide de l'instruction Python class. Nous utilisons (ou abusons ?) de l'instruction class pour définir les interfaces, mais pour faire d'une classe une interface elle doit hériter de zope.interface.Interface. La convention dans Zope est d'utiliser en préfixe la lettre I pour une Interface.
Déclarer les interfaces
Pour expliquer d'autres concepts, regardons à cette interface :
class IHost(interface.Interface):
"""Un objet hôte"""
name = interface.Attribute("""Nom de l'invité""")
def goodmorning(name):
"""Dit bonjour à l'invité"""
L'interface IHost a deux attributs, name et goodmorning, rappellez vous que, au moins en Python, les méthodes sont aussi des attributs de classe.
Ici nous définissons d'abord l'interface d'un attribut qui n'est pas une méthode (name). L'attribut name est définit à l'aide de la classe zope.interface.Attribute. Il est important de rappeler que nous n'écrivons pas une classe (les détails de l'implémentation d'un objet) mais une interface ou la définition d'une classe. Quand vous ajoutez l'attribut name à l'interface IHost vous ne spécifiez pas de valeur initiale, en effet l'intérêt de définir l'attribut name est d'indiquer que n'importe quel implémentation de cette interface fournira un attribut name. Dans ce cas vous n'avez même pas à dire de quel type l'attribut sera.
Les interfaces sont les empreintes et les notes de conceptions d'une classe. Par conséquent, les interfaces peuvent être réutilisé pour fournir la documentation de l'API et des astuces d'utilisation. Vous pouvez passer la documentation d'un attribut comme premier argument de Attribute. L'autre attribut, goodmorning, est une méthode définie à l'aide d'une définition de fonction. Notez que l'argument self est inutile pour une interface.
Maintenant vous allez voir comment sont connectés les interfaces, les classes et les objets. Les objets sont les choses réelle, les objets sont les instances des classes. L'interface est la définition réelle de l'objet, donc les classes sont juste les détails d'implémentation. C'est pourquoi vous devriez programmer pour une interface et pas pour une implémentation.
Maintenant vous devez vous familiariser avec deux termes pour comprendres les concepts subséquent. Le est premier fournit (provide) et le second implémente (implement).
Les objets fournissent une interface tandis que les classes implémentes une interface. En d'autres mots, les objets fournissent des interfaces que leur classes implémentent. Dans l'exemple précédent host (objet) fournit IHost (interface) et Host (classe) implémente IHost (interface).
Un objet peut aussi fournir plus d'une interface et une classe peut implémenter plus d'une interface. Les objets peuvent aussi fournir les objets directement (directly), en plus de ce que leur classe implémente.
NDTR : les mots provide (fournir), implement (implémente) et directly (directement) sont utilisé dans l'API de Zope et devraient être retenu par le lecteur francophone.
Note : Les classes sont les détails d'implémentation des objets. En Python, une classes est un objet appelable (callable), alors pourquoi les autres objets appelable ne pouraient pas implémenter d'interface? En fait, c'est possible. Pour n'importe quel objet appelable vous pouvez déclarer qu'il peut produire des objets qui fournissent une interface en disant que l'objet appelable implémente l'interface. Les objets appelables sont généralement appelés fabrique (factory).
Implémenter les interfaces
Pour déclarer qu'une classe implémente une interface particulière, utilisez la fonction zope.interface.implements dans la déclaration de classe.
Dans cet exemple Host implémente IHost:
from zope import interface
from interfaces import IHost
class Host(object):
interface.implements(IHost)
name = u''
def goodmorning(self, name):
"""Dit bonjour à l'invité"""
return "Bonjour %s" % name
Etant donné que Host implémente IHost, les instances de Host fournissent IHost. Il y a quelques méthodes pour faire de l'introspection sur la déclaration. La déclaration peut aussi se faire en dehors de la classe. Si vous n'écrivez pas interfaces.implements(IHost) dans l'exemple ci-dessus, vous pouvez toujours le faire après la définition de la classe :
>>> interface.classImplements(Host, IHost)
Ceci peut servir à utiliser des composants qui n'ont aucun rapport avec Zope.
Les invariants
Considerez cette exemple simple, un objet "personne". Un objet pesonne a un nom, un email et un numéro de téléphone qui seront stoqués sous forme d'attributs name, email et phone. Comment pourrait-on poser une condition de validité tel que l'email ou le numéro de téléphone doivent être fournit pour que les objets aient un sens, mais pas nécessairement les deux ?
D'abord nous allons définir un objet appelable pour spécifier la condition, il peut s'agir d'une simple fonction ou d'une instance de classe appelable :
>>> def contacts_invariant(obj):
... if not (obj.email or obj.phone):
... raise Exception("Au moins une information de contact est requise")
Vous pouvez ensuite définir l'interface de l'objet personne. Utilisez la fonction interface.invariant pour fixer les invariants :
>>> class IPerson(interface.Interface):
...
... name = interface.Attribute("Nom")
... email = interface.Attribute("Adresse email")
... phone = interface.Attribute("Numéro de téléphone")
...
... interface.invariant(contacts_invariant)
Maintenant vous pouvez utiliser la méthode validateInvariants de l'interface pour la valider :
>>> class Person(object):
... interface.implements(IPerson)
...
... name = None
... email = None
... phone = None
>>> jack = Person()
>>> jack.email = u"jack@some.address.com"
>>> IPerson.validateInvariants(jack)
>>> jill = Person()
>>> IPerson.validateInvariants(jill)
Traceback (most recent call last):
...
Exception: Au moins une information de contact est requise
L'utilisation de validateInvariants peut parraître étrange mais celà est cohérent car l'invariant se porte sur les objets fournissant une interface. Zope 3 utilise automatiquement cette méthode pour valider les entrées utilisateur quelque soit la classe qui implémente l'interface.

