-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathizca-fr.txt
5016 lines (3624 loc) · 149 KB
/
izca-fr.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
========================================================
Le guide complet de l'Architecture de Composants de Zope
========================================================
:Author: Baiju M
:Version: 0.5.8
:Printed Book: `http://www.lulu.com/content/1561045
<http://www.lulu.com/content/1561045>`_
:Online PDF: `http://www.muthukadan.net/docs/zca.pdf
<http://www.muthukadan.net/docs/zca.pdf>`_
:Traducteur: Christophe Combelles <ccomb@free.fr>, Stéphane Klein <stephane@harobed.org>
Copyright (C) 2007,2008,2009 Baiju M <baiju.m.mail AT gmail.com>.
Chacun est autorisé à copier, distribuer et/ou modifier ce document
suivant les termes de la « GNU Free Documentation License », version 1.3
ou toute version ultérieure publiée par la Free Software
Foundation.
Le code source présent dans ce document est soumis aux conditions de
la « Zope Public License », Version 2.1 (ZPL).
THE SOURCE CODE IN THIS DOCUMENT AND THE DOCUMENT ITSELF IS PROVIDED
"AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE,
MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR
PURPOSE.
.. sidebar:: Remerciements
De nombreuses personnes m'ont aidé à écrire ce livre. Le brouillon initial a été
relu par mon collègue Brad Allen. Quand j'ai annoncé ce livre sur mon blog, j'ai
reçu de nombreux encouragements pour poursuivre ce travail. Kent Tenney a
modifié de nombreuses parties du livre et a également réécrit l'application
servant d'exemple. De nombreuses autres personnes m'ont envoyé des corrections
et des commentaires, dont Lorenzo Gil Sanchez, Michael Haubenwallner, Nando
Quintana, Stephane Klein, Tim Cook, Kamal Gill et Thomas Herve. Lorenzo a
traduit ce travail en espagnol et Stephane initié la traduction en français. Merci à
toutes ces personnes !
.. contents::
.. .. sectnum::
Mise en route
-------------
Introduction
~~~~~~~~~~~~
Le développement de grands systèmes logiciels est toujours une tâche ardue
L'expérience montre que l'approche orientée objet est bien appropriée à
l'analyse, à la modélisation et à la programmation des systèmes complexes. La
conception orientée composants et la programmation basée sur des composants
deviennent actuellement très populaires. L'approche basée composants est d'une
grande aide pour l'écriture et la maintenance de systèmes logiciels et
de leurs tests unitaires. Il existe de nombreux frameworks permettant la
conception basée sur des composants dans différents langages, certains étant
même indépendants du langage. On peut citer comme exemple : COM de Microsoft, ou
XPCOM de Mozilla.
La **Zope Component Architecture (ZCA)** est un framework en Python qui autorise
la conception et la programmation basée sur des composants. Elle est très bien
adaptée au développement de grands systèmes logiciels. La ZCA n'est
pas liée au serveur d'application Zope : elle peut être utilisée pour développer
n'importe quelle application en Python. Peut-être devrait-elle s'appeler la «
Python Component Architecture ».
Le but de la ZCA est d'utiliser des objets Python de manière efficace. Les
composants sont des objets réutilisables fournissant une interface que l'on peut
introspecter. Une interface est un objet qui décrit comment on peut travailler
avec un composant particulier. Autrement dit, un composant fournit une interface
implémentée dans une classe ou tout autre objet appelable (*callable*). La façon
dont le composant est implémenté n'a pas d'importance : ce qui compte est que
celui-ci soit en accord avec l'interface. Grâce à la ZCA, vous pouvez éclater la
complexité d'un système dans plusieurs composants travaillant ensemble. Elle
vous aide à créer notamment deux types de composants : les « adaptateurs » et les «
utilitaires ».
Trois paquets composent la ZCA :
- ``zope.interface`` est utilisé pour la définition d'une interface.
- ``zope.event`` fournit un système simple d'événements.
- ``zope.component`` sert à la création, l'inscription et la récupération des
composants.
Souvenez-vous que la ZCA ne fournit pas de composants par elle-même. Elle n'a
pour but que de créer, inscrire et récupérer des composants. Gardez également à
l'esprit qu'un adaptateur est une classe Python tout à fait normale (en général
une fabrique) et qu'un utilitaire est un simple objet Python appelable.
Le framework ZCA est développé comme une partie du projet Zope 3. Comme
mentionné plus haut, c'est un framework purement Python et il peut être utilisé
dans n'importe quelle application Python. Actuellement, Zope 3, Zope 2 et Grok
l'utilisent abondamment. De nombreux autres projets l'utilisent, y compris des
applications non liées au web [#projects]_.
.. [#projects] http://wiki.zope.org/zope3/ComponentArchitecture
Petit historique
~~~~~~~~~~~~~~~~
Le développement de la ZCA a débuté en 2001 dans le cadre du projet Zope 3. Ce
framework est le fruit de l'expérience acquise pendant le développement
d'applications d'envergure avec Zope 2. Jim Fulton est le père de ce projet. De
nombreuses personnes ont participé à sa conception et à son implémentation,
notamment Stephan Richter, Philipp von Weitershausen, Guido van Rossum (*aka.
Python BDFL*), Tres Seaver, Phillip J Eby et Martijn Faassen.
À ses débuts, la ZCA définissait des types de composants supplémentaires : les
*services* et les *vues*, mais les développeurs ont fini par réaliser qu'un
*utilitaire* peut remplacer un *service* et qu'un *multi-adaptateur* peut
remplacer une *vue*. Aujourd'hui, la ZCA comporte un nombre très réduit de types
de composants de base : les *utilitaires* (en anglais *utilities*), les *adaptateurs*
(*adapters*), les *abonnés* (*subscribers*) et les *gestionnaires* (*handlers*).
Et encore, les *abonnés* et les *gestionnaires* sont des cas particuliers de
l'*adaptateur*.
Pendant le cycle de développement de Zope 3.2, Jim Fulton a proposé une
importante simplification de la ZCA [#proposal]_. Grâce à cette simplification,
une nouvelle interface unique (`IComponentRegistry`) a été créée pour
l'inscription des composants globaux et locaux.
.. [#proposal] http://wiki.zope.org/zope3/LocalComponentManagementSimplification
Le paquet ``zope.component`` avait une longue liste de dépendances, dont une
grande partie n'était pas nécessaire pour une application non liée à Zope 3.
Pendant PyCon 2007, Jim Fulton a ajouté la fonctionnalité `extras_require` à
setuptools pour permettre d'extraire le cœur de la ZCA des fonctionnalités
annexes [#extras]_.
.. [#extras] http://peak.telecommunity.com/DevCenter/setuptools#declaring-dependencies
En mars 2009, Tres Seaver a supprimé les dépendances à ``zope.deferredimport`` et
``zope.proxy``.
Aujourd'hui, le projet ZCA est indépendant et possède son propre cycle de
publications et son propre dépôt Subversion. Il est fourni avec le framework
Zope. [#framework]_. Cependant, les anomalies et les bogues sont toujours pris
en charge au travers du projet Zope 3 [#bugs]_ et la liste de diffusion
zope-dev est utilisée pour débattre du développement [#discussions]_. Il existe
également une liste pour les utilisateurs de Zope 3 (`zope3-users`) qui peut
être utilisée pour toute demande au sujet de la ZCA [#z3users]_.
.. [#framework] http://docs.zope.org/zopeframework/
.. [#bugs] https://bugs.launchpad.net/zope3
.. [#discussions] http://mail.zope.org/mailman/listinfo/zope-dev
.. [#z3users] http://mail.zope.org/mailman/listinfo/zope3-users
Installation
~~~~~~~~~~~~
Les paquets ``zope.component``, ``zope.interface`` et ``zope.event`` constituent
le cœur de la *Zope Component Architecture*. Ils fournissent des outils
permettant de définir, inscrire et rechercher des composants. Le paquet
``zope.component`` et ses dépendances sont disponibles sous forme d'*egg* dans
l'index des paquets Python (PyPI) [#pypi]_.
.. [#pypi] Dépôt des paquets Python : http://pypi.python.org/pypi
Vous pouvez installer ``zope.component`` et ses dépendances avec `easy_install`
[#easyinstall]_ ::
$ easy_install zope.component
.. [#easyinstall] http://peak.telecommunity.com/DevCenter/EasyInstall
Cette commande télécharge ``zope.component`` et ses dépendances depuis PyPI et
l'installe dans votre *Python path*.
Une autre manière de procéder est de télécharger ``zope.component`` et ses
dépendances depuis PyPI et de les installer. Il faut installer les paquets dans
l'ordre donné ci-dessous. Sous Windows, vous aurez probablement besoin du paquet
binaire de ``zope.interface``.
1. ``zope.interface``
2. ``zope.event``
3. ``zope.component``
Pour installer ces paquet, après téléchargement, vous pouvez utiliser la
commande ``easy_install`` avec les eggs comme argument (éventuellement en
fournissant tous les eggs sur la même ligne de commande) ::
$ easy_install /path/to/zope.interface-3.x.x.tar.gz
$ easy_install /path/to/zope.event-3.x.x.tar.gz
$ easy_install /path/to/zope.component-3.x.x.tar.gz
Vous pouvez aussi installer ces paquets en les décompressant séparément. Par
exemple ::
$ tar zxvf /path/to/zope.interface-3.x.x.tar.gz
$ cd zope.interface-3.x.x
$ python setup.py build
$ python setup.py install
Ces méthodes installeront la ZCA dans votre *Python système*, dans le dossier
``site-packages``, ce qui peut être problématique. Dans un message sur la liste
de diffusion Zope 3, Jim Fulton déconseille d'utiliser le Python système
[#systempython]_. Vous pouvez utiliser ``virtualenv`` ou ``zc.buildout`` pour
jouer avec n'importe quel paquet Python, et également pour les déploiements.
.. [#systempython] http://article.gmane.org/gmane.comp.web.zope.zope3/21045
Méthode pour expérimenter
~~~~~~~~~~~~~~~~~~~~~~~~~
Il existe deux approches populaires en Python pour construire un environnement
de travail isolé. La première est ``virtualenv`` créé par Ian Biking, la
deuxième est ``zc.buildout`` créé par Jim Fulton. Il est même possible
d'utiliser ces paquets ensemble. Ils vous aideront à installer
``zope.component`` et d'autres dépendances dans un environnement de
développement isolé. Passer un peu de temps à se familiariser avec ces outils
vous sera bénéfique lors des développements et des déploiements.
**virtualenv**
Vous pouvez installer ``virtualenv`` grâce à ``easy_install`` ::
$ easy_install virtualenv
Puis vous pouvez créer un nouvel environnement de cette façon ::
$ virtualenv --no-site-packages myve
Ceci crée un nouvel environnement virtuel dans le dossier ``myve``.
Maintenant, depuis ce dossier, vous pouvez installer ``zope.component`` et ses
dépendances avec le script ``easy_install`` situé dans ``myve/bin`` ::
$ cd myve
$ ./bin/easy_install zope.component
Ensuite vous pouvez importer ``zope.interface`` et ``zope.component`` depuis
le nouvel interprète ``python`` situé dans ``myve/bin`` ::
$ ./bin/python
Cette commande démarre un interprète Python que vous pourrez utiliser pour
lancer le code de ce livre.
**zc.buildout**
En combinant ``zc.buildout`` et la recette ``zc.recipe.egg``, vous pouvez créer
un interprète Python ayant accès aux eggs Python spécifiés. Tout d'abord,
installez ``zc.buildout`` grâce à la commande ``easy_install`` (vous pouvez le
faire à l'intérieur de l'environnement virtuel). Pour créer un nouveau
*buildout* servant à expérimenter des eggs Python, commencez par créer un
dossier, puis initialisez-le grâce à la commande ``buildout init`` ::
$ mkdir mybuildout
$ cd mybuildout
$ buildout init
De cette façon, le dossier ``mybuildout`` devient un « buildout ». Le fichier de
configuration de buildout est par défaut `buildout.cfg`. Après initialisation,
il doit contenir les lignes suivantes ::
[buildout]
parts =
Vous pouvez le modifier pour qu'il corresponde à ceci ::
[buildout]
parts = py
[py]
recipe = zc.recipe.egg
interpreter = python
eggs = zope.component
Si maintenant vous lancez la commande ``buildout`` disponible à l'intérieur du
dossier ``mybuildout/bin``, vous obtiendrez un nouvel interprète Python dans le
dossier ``mybuildout/bin`` ::
$ ./bin/buildout
$ ./bin/python
Cette commande démarre un interprète Python que vous pourrez utiliser pour
lancer le code de ce livre.
Un exemple
----------
Introduction
~~~~~~~~~~~~
Imaginez une application servant à enregistrer les clients d'un hôtel. En
Python, il est possible de faire ceci de différentes manières. Nous allons
commencer par étudier rapidement la méthode procédurale, puis nous diriger vers
une approche basique orientée objet. En examinant l'approche orientée objet,
nous verrons comment nous pouvons bénéficier des motifs de conception
`adaptateur` et `interface`. Cela nous fera entrer dans le monde de la Zope
Component Architecture.
Approche procédurale
~~~~~~~~~~~~~~~~~~~~
Dans toute application professionnelle, le stockage des données est un point
critique. Pour une question de simplicité, cet exemple utilise un simple
dictionnaire Python comme méthode de stockage. Nous allons générer des
identifiants uniques pour le dictionnaire, la valeur associée sera elle-même un
dictionnaire contenant les détails de l'enregistrement.
>>> bookings_db = {} #key: unique ID, value: details in a dictionary
Une implémentation minimaliste nécessite une fonction à laquelle on transmet les
détails de l'enregistrement et une fonction annexe qui fournit les
identifiants uniques utilisés comme clés du dictionnaire de stockage.
Nous pouvons obtenir un identifiant unique de cette façon ::
>>> def get_next_id():
... db_keys = bookings_db.keys()
... if db_keys == []:
... next_id = 1
... else:
... next_id = max(db_keys) + 1
... return next_id
Comme vous pouvez voir, l'implémentation de la fonction `get_next_id` est très
simple. La fonction récupère une liste de clés et vérifie si la liste est vide.
Si c'est le cas, nous savons que nous en sommes au premier enregistrement et
nous renvoyons `1`. Sinon, nous ajoutons `1` à la valeur maximale de la liste et
nous renvoyons la nouvelle valeur en résultant.
Ensuite, pour créer des enregistrements dans le dictionnaire bookings_db, nous
utilisons la fonction suivante ::
>>> def book_room(name, place):
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': name,
... 'room': place
... }
Une application de gestion des enregistrements d'hôtel a besoin de données
supplémentaires :
- les numéros de téléphone
- les options des chambres
- les méthodes de paiement
- ...
Et de code pour gérer les données :
- annuler une réservation
- Modifier une réservation
- payer une chambre
- stocker les données
- assurer la sécurité des données
- ...
Si nous devions continuer l'exemple procédural, il faudrait créer plusieurs
fonctions, en renvoyant les données de l'une à l'autre. Au fur à mesure que les
fonctionnalités seraient ajoutées, le code deviendrait de plus en plus difficile
à maintenir et les bogues deviendraient difficiles à trouver et à corriger.
Nous n'irons pas plus loin dans l'approche procédurale. Il sera beaucoup plus
facile d'assurer la persistance des données, la flexibilité de la conception et
l'écriture de tests unitaires en utilisant des objets.
Approche orientée objet
~~~~~~~~~~~~~~~~~~~~~~~
Notre discussion sur la conception orientée objet nous amène à la notion de
« classe » qui sert à encapsuler les données et le code qui les gère.
Notre classe principale sera « FrontDesk ». Le FrontDesk et d'autres classes
auxquelles il délèguera du traitement, sauront comment gérer les données de
l'hôtel. Nous allons créer des instances de FrontDesk pour appliquer cette
connaissance au métier de la gestion d'hôtel.
L'expérience a montré qu'en consolidant le code et les contraintes sur les
données via des objets, on aboutit à un code qui est plus facile à comprendre, à
tester et à modifier.
Observons les détails d'implémentation de la classe FrontDesk ::
>>> class FrontDesk(object):
...
... def book_room(self, name, place):
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': name,
... 'place': place
... }
Dans cette implémentation, l'objet `frontdesk` (une instance de la classe
FrontDesk) est capable de prendre en charge les réservations. Nous pouvons
l'utiliser de la façon suivante ::
>>> frontdesk = FrontDesk()
>>> frontdesk.book_room("Jack", "Bangalore")
Dans tout projet, on est confronté à des changements de besoins. Dans notre cas,
la direction a décidé que chaque client devait fournit un numéro de téléphone,
donc nous devons changer le code.
Nous pouvons satisfaire ce nouveau besoin en ajoutant un argument à la méthode
`book_room` qui sera ajouté au dictionnaire des valeurs ::
>>> class FrontDesk(object):
...
... def book_room(self, name, place, phone):
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': name,
... 'place': place,
... 'phone': phone
... }
En plus de devoir migrer les données vers le nouveau schéma, nous devons changer
tous les appels à la classe ``FrontDesk``. Si nous incorporons les coordonnées
de chaque client dans un objet et que nous utilisons cet objet pour les
réservations, les modifications de code pourront être minimisées. Nous pouvons
maintenant changer les détails de l'objet client sans avoir à changer les appels
à FrontDesk.
Nous avons donc ::
>>> class FrontDesk(object):
...
... def book_room(self, guest):
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': guest.name,
... 'place': guest.place,
... 'phone': guest.phone
... }
Nous devons toujours modifier le code pour répondre aux nouvelles demandes.
C'est inévitable, cependant notre but est de minimiser ces changements et donc
d'améliorer la maintenabilité.
.. note::
Lors du développement il ne faut jamais hésiter à faire des changements sans
craindre de casser l'application. L'avertissement nécessaire doit être
immédiatement obtenu grâce à des tests automatisés. Avec des tests bien écrits
(et un bon contrôle de versions), vous pouvez faire impunément des changements
aussi importants que vous souhaitez. Une bonne source d'informations à propos de
cette philosophie de programmation est l'ouvrage `Extreme Programming Explained`
par Kent Beck.
En introduisant l'objet guest, vous avez économisé un peu de temps. Mais plus
important, l'abstraction apportée par l'objet guest a rendu le système plus
simple et mieux compréhensible. Par conséquent, le code est plus facile à
restructurer et à maintenir.
Le motif `adaptateur`
~~~~~~~~~~~~~~~~~~~~~
Dans une vraie application, l'objet frontdesk devra gérer des fonctionnalités
comme les annulations et les modifications. Avec la conception actuelle, nous
devons transmettre l'objet `guest` au frontdesk à chaque fois que nous appelons
les méthodes `cancel_booking` et `update_booking`.
Nous pouvons éviter ceci si nous transmettons l'objet `guest` à
FrontDesk.__init__() et si nous le stockons en attribut de l'instance.
>>> class FrontDeskNG(object):
...
... def __init__(self, guest):
... self.guest = guest
...
... def book_room(self):
... guest = self.guest
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': guest.name,
... 'place': guest.place,
... 'phone': guest.phone
... }
...
... def cancel_booking(self):
... guest = self.guest
... #code for cancellations goes here ...
...
... def update_booking(self):
... guest = self.guest
... #code for updatiion goes here ...
La solution que nous avons obtenue est un motif de conception courant appelé
`adaptateur`. Le `Gang of Four` [#patternbook]_ résume ainsi l'*esprit* de
l'adaptateur ::
« Convertir l'interface d'une classe en une autre interface
à laquelle le client s'attend. L'adaptateur permet à des
classes de fonctionner ensemble même si elles ont des
interfaces incompatibles. »
En général, un objet adaptateur *contient* un objet adapté ::
>>> class Adapter(object):
...
... def __init__(self, adaptee):
... self.adaptee = adaptee
Ce motif sera utile lorsqu'on sera confronté à des détails d'implémentation qui
dépendent de considérations telles que:
- modification des besoins client
- modification des méthodes de stockage (ZODB, RDBM, XML)
- modification des méthodes de sortie (HTML, PDF, texte pur)
- modification de la source utilisée (ReST, Markdown, Textile)
La ZCA utilise des adaptateurs et un *registre de composants* pour fournir la
capacité de changer les détails d'implémentation du code via de la
*configuration*.
Comme nous pouvons le constater dans la section sur les adaptateurs de la ZCA,
la capacité de configurer les détails d'implémentation fournit des avantages
intéressants :
- on peut interchanger les implémentations
- on peut ajouter de nouvelles implémentations si besoin
- on améliore les possibilités de réutilisation, aussi bien d'un code existant que
du code utilisant la ZCA.
Ces possibilités mènent à du code qui est flexible, évolutif et réutilisable. Il
y a un cependant un coût : maintenir le registre de composants ajoute un niveau
de complexité à l'application. Si une application n'a pas besoin de ces
avantages, la ZCA n'est pas pas nécessaire.
Nous sommes maintenant prêts à étudier la Zope Component Architecture, en
commençant avec les interfaces.
Interfaces
----------
Introduction
~~~~~~~~~~~~
Le fichier README.txt [#readmes]_ dans chemin/vers/zope/interface définit les
interfaces de cette manière ::
Les interfaces sont des objets qui spécifient (documentent) le
comportement externe des objets qui les « fournissent ».
Une interface spécifie un comportement au travers :
- de documentation informelle dans une docstring
- de définitions d'attributs
- d'invariants qui sont des conditions posées sur les objets
fournissant l'interface.
L'ouvrage de référence `Design Patterns` [#patternbook]_ par le `Gang of Four`
recommande que vous devez « programmer pour une interface, pas pour une
implémentation ». Définir une interface formelle est utile dans la compréhension
d'un système. De plus, les interfaces vous apportent tous les bénéfices de la
ZCA.
.. [#readmes] L'arborescence du code de Zope contient de nombreux fichiers README.txt
qui offrent une très bonne documentation.
.. [#patternbook] http://en.wikipedia.org/wiki/Design_Patterns
Une interface spécifie les caractéristiques d'un objet, son comportement et ses
capacités. Elle décrit le « quoi » d'un objet. Pour apprendre le « comment »,
vous devez regarder l'implémentation.
Les métaphores couramment utilisées pour les interfaces sont `contrat` ou
`plan`, des termes légaux et architecturaux pour représenter un jeu de
spécifications.
Dans certains langages de programmation comme Java, C# ou VB.NET, les
interfaces sont un aspect explicite du langage. Étant donné que Python ne
possède pas nativement d'interfaces, la ZCA les implémente comme des
meta-classes desquelles on hérite.
Voici un exemple classique de Hello World ::
>>> class Host(object):
...
... def goodmorning(self, name):
... """Say good morning to guests"""
...
... return "Good morning, %s!" % name
Dans la classe ci-dessus, on a défini une méthode `goodmorning`. Si vous appelez
cette méthode depuis un objet créé en utilisant cette classe, vous obtiendrez un
`Good morning, ...!` ::
>>> host = Host()
>>> host.goodmorning('Jack')
'Good morning, Jack!'
Ici, ``host`` est l'objet réel que votre code utilise. Si vous voulez
examiner les détails d'implémentation, vous devez accéder à la classe ``Host``,
soit via le code source, soit au travers d'un outil de documentation d'API
[#api]_.
.. [#api] http://en.wikipedia.org/wiki/Application_programming_interface
Maintenant nous allons commencer à utiliser les interfaces de la ZCA. Pour la
classe ci-dessus, vous pouvez définir l'interface comme suit ::
>>> from zope.interface import Interface
>>> class IHost(Interface):
...
... def goodmorning(guest):
... """Say good morning to guest"""
Vous pouvez constater que l'interface hérite de zope.interface.Interface. C'est
de cette façon (abusive ?) que la ZCA définit des interfaces. Le préfixe « I »
pour le nom de la classe est une convention utile.
Déclarer des interfaces
~~~~~~~~~~~~~~~~~~~~~~~
Vous avez déjà vu comment déclarer une interface en utilisant ``zope.interface``
dans la section précédente. Cette section va expliquer les concepts en détail.
Prenez cette exemple d'interface ::
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> class IHost(Interface):
... """A host object"""
...
... name = Attribute("""Name of host""")
...
... def goodmorning(guest):
... """Say good morning to guest"""
L'interface ``IHost`` possède deux attributs, ``name`` et ``goodmorning``.
Rappelez-vous qu'en Python les méthodes des classes sont aussi des attributs.
L'attribut ``name`` est défini en utilisant la classe
``zope.interface.Attribute``. Quand vous ajoutez l'attribut ``name`` à
l'interface ``IHost``, vous ne définissez pas de valeur initiale. La raison de
définir l'attribut ``name`` ici est simplement d'indiquer que toute
implémentation de cette interface doit fournir un attribut nommé ``name``. Dans
ce cas, vous n'indiquez même pas de quel type doit être l'attribut ! Vous pouvez
juste fournir une chaîne de documentation comme premier argument à
``Attribute``.
L'autre attribut, ``goodmorning`` est une méthode définie en utilisant une
fonction. Notez bien que ``self`` n'est pas nécessaire dans les interfaces, car
``self`` est un détail d'implémentation de la classe. Il est possible pour un
module d'implémenter cette interface. Si un module implémente cette interface,
cela signifiera qu'un attribut ``name`` et une fonction ``goodmorning`` seront
définis. Et la fonction ``goodmorning`` devra accepter un argument.
Nous allons maintenant voir comment effectuer le lien entre les objets, les
classes et les interfaces. Seuls les objets sont vivants : ce sont des instances
de classes. Et les interfaces représentent la définition des objets, donc les
classes ne sont qu'un détail d'implémentation. C'est pour cette raison que vous
devez programmer pour une interface, pas pour une implémentation.
Nous devons nous familiariser avec deux termes supplémentaires pour comprendre
les autres concepts. Le premier est `fournir`, le deuxième est `implémenter`.
Les objets fournissent des interfaces, tandis que les classes implémentent des
interfaces. Autrement dit, les objets fournissent les interfaces que les classes
implémentent. Dans l'exemple ci-dessus, l'objet ``host`` fournit l'interface
``IHost`` et la classe ``Host`` implémente l'interface ``IHost``. Un objet peut
fournir plusieurs interfaces. Les objets peuvent également fournir directement
des interfaces, en plus de celles implémentées par leur classe.
.. note::
Les classes sont les détails d'implémentation des objets. En Python, les
classes sont des objets appelables (`callable`). Pourquoi un autre objet
appelable ne pourrait-il pas implémenter une interface ? En fait c'est possible.
Pour n'importe quel objet appelable, vous pouvez déclarer qu'il produit des
objets qui fournissent une interface donnée, en disant simplement que cet objet
appelable implémente l'interface. Ces objets appelables sont généralement
nommés des « fabriques ». Étant donné que les fonctions sont des objets
appelables, une fonction peut implémenter une interface.
Implémentation des interfaces
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pour déclarer qu'une classe implémente une interface donnée, utilisez la
fonction ``zope.interface.implements`` dans la déclaration de classe.
Considérons cet exemple. Ici, ``Host`` implémente ``IHost`` ::
>>> from zope.interface import implements
>>> class Host(object):
...
... implements(IHost)
...
... name = u''
...
... def goodmorning(self, guest):
... """Say good morning to guest"""
...
... return "Good morning, %s!" % guest
.. note::
Si vous vous demandez comment ``implements`` fonctionne, faites un tour sur
l'article de blog de James Henstridge
(http://blogs.gnome.org/jamesh/2005/09/08/python-class-advisors/) .
Dans la section sur les adaptateurs, vous allez voir une fonction ``adapts``,
celle-ci fonctionne de manière similaire.
Comme ``Host`` implémente ``IHost``, les instances de ``Host`` fournissent
``IHost``. Il existe des méthodes permettant d'introspecter les déclarations. La
déclaration peut également s'écrire en dehors de la classe. Au lieu d'écrire
``interface.implements(IHost)`` comme dans l'exemple ci-dessus, vous pouvez
écrire la chose suivante après la déclaration de classe ::
>>> from zope.interface import classImplements
>>> classImplements(Host, IHost)
L'exemple revisité
~~~~~~~~~~~~~~~~~~
Maintenant retournons à notre application exemple. Voici comment définir
l'interface de l'objet frontdesk ::
>>> from zope.interface import Interface
>>> class IDesk(Interface):
... """A frontdesk will register object's details"""
...
... def register():
... """Register object's details"""
...
Nous avons d'abord importé la classe ``Interface`` depuis le module
``zope.interface``. Si vous définissez une sous-classe de cette classe
``Interface``, ce sera considéré comme une interface, du point de vue de
l'Architecture de Composants. Une interface peut être implémentée, comme nous
l'avons vu, dans une classe ou tout autre objet appelable.
L'interface de frontdesk ici définie est ``IDesk``. La chaîne de documentation
de l'interface donne une idée sur la nature de l'objet. En définissant une
méthode dans l'interface, vous avez passé un contrat avec le composant, imposant
la présence d'une méthode du même nom. Dans la déclaration de la méthode côté
interface, le premier argument ne doit pas être `self`, car une interface ne
sera jamais instanciée et ses méthodes ne seront jamais appelées. Le rôle d'une
interface est simplement de documenter quels arguments et quelles méthodes
doivent apparaître dans une classe qui l'implémente, et le paramètre `self`
n'est qu'un détail d'implémentation qui n'a pas besoin d'être documenté.
Comme vous le savez, une interface peut aussi spécifier des attributs normaux ::
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> class IGuest(Interface):
...
... name = Attribute("Name of guest")
... place = Attribute("Place of guest")
Dans cette interface, l'objet guest a deux attributs contenant de la
documentation. Une interface peut spécifier à la fois des méthodes et des
attributs. Une interface peut être implémentée dans une classe, un module ou
tout autre objet. Par exemple une fonction peut créer dynamiquement le composant
et le renvoyer, auquel cas on dit que la fonction implémente l'interface.
Vous savez maintenant ce qu'est une interface, comment la créer et l'utiliser.
Dans le chapitre suivant nous allons voir comment une interface peut être
utilisée pour définir un composant adaptateur.
Interfaces marqueurs
~~~~~~~~~~~~~~~~~~~~
Une interface peut être utilisée pour déclarer qu'un objet appartient à un
type donné. Une interface sans aucun attribut ni aucune méthode est appelée une
« interface marqueur ».
Voici une `interface marqueur` ::
>>> from zope.interface import Interface
>>> class ISpecialGuest(Interface):
... """A special guest"""
Cette interface peut être utilisée pour déclarer qu'un objet est un client
spécial.
Invariants
~~~~~~~~~~
Parfois vous aurez besoin de définir des règles ou des contraintes sur les
attributs de vos composants. Ces types de règles sont appelés des `invariants`.
Vous pouvez utiliser ``zope.interface.invariant`` pour définir des
``invariants`` sur vos objets dans leur interface.
Considérons un exemple simple, avec un objet `person`. Une objet `person` a un
attribut `nom`, un attribut `email` et un attribut `phone`. Comment peut-on
implémenter une règle de validation qui oblige à définir au choix le `phone` ou
l'`email` mais pas forcément les deux ?
Créez tout d'abord un objet appelable, soit une simple fonction soit une
instance appelable d'une classe de la façon suivante ::
>>> def contacts_invariant(obj):
...
... if not (obj.email or obj.phone):
... raise Exception(
... "At least one contact info is required")
Ensuite définissez l'interface de l'objet `person` en utilisant la fonction
``zope.interface.invariant`` pour définir l'invariant ::
>>> 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)
Maintenant, utilisez la méthode `validateInvariants` de l'interface pour
effectuer la validation ::
>>> from zope.interface import implements
>>> class Person(object):
... 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: At least one contact info is required
Vous constatez que l'ojet `jack` est validé sans erreur. Mais l'objet `jill` n'a
pas pu valider la contrainte de l'invariant, il a donc levé une exception.
Adaptateurs
-----------
Implémentation
~~~~~~~~~~~~~~
Cette section décrit les adaptateurs en détail. L'Architecture de Composants,
comme vous l'avez remarqué, fournit une aide dans l'utilisation efficace des objets
Python. Les composants adaptateurs sont l'un des composants basiques utilisés
par l'Architecture de Composants de Zope pour utiliser efficacement des objets
Python. Les adaptateurs sont aussi des objets Python, mais avec une interface bien
définie.
Pour déclarer qu'une classe est un adaptateur, utilisez la fonction `adapts`
définie dans le paquet ``zope.component``. Voici un nouvel adaptateur
`FrontDeskNG` avec une déclaration explicite d'interface ::
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
... implements(IDesk)
... adapts(IGuest)
...
... def __init__(self, guest):
... self.guest = guest
...
... def register(self):
... guest = self.guest
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': guest.name,
... 'place': guest.place,
... 'phone': guest.phone
... }
Ce que nous avons défini ici est un `adaptateur` pour `IDesk`, qui s'adapte à
l'objet `IGuest`. L'interface `IDesk` est implémentée par la classe
`FrontDeskNG`. Donc une instance de cette classe fournira l'interface `IDesk`.
::
>>> class Guest(object):
...
... implements(IGuest)
...
... def __init__(self, name, place):
... self.name = name
... self.place = place
>>> jack = Guest("Jack", "Bangalore")
>>> jack_frontdesk = FrontDeskNG(jack)
>>> IDesk.providedBy(jack_frontdesk)
True
`FrontDeskNG` est juste un adaptateur que nous avons créé. Vous pouvez créer
d'autres adaptateurs qui prendront en charge le bureau d'enregistrement différemment.
Inscription
~~~~~~~~~~~
Pour utiliser ce composant adaptateur, vous devez l'inscrire dans un registre de
composants (appelé également « gestionnaire de site »). Un gestionnaire de site
réside normalement à l'intérieur d'un site. Le site et le gestionnaire de site
prendront leur importance lors du développement d'une application Zope 3. Pour
l'instant vous avez juste besoin de connaître les notions de site global et de
gestionnaire global de site (ou registre de composant). Un gestionnaire global
de site est situé en mémoire, alors qu'un gestionnaire de site local est
persistant.
Pour inscrire votre composant, commencez par récupérer le gestionnaire global de
site ::
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(FrontDeskNG,
... (IGuest,), IDesk, 'ng')
Pour récupérer le gestionnaire global de site, vous devez appeler la fonction
``getGlobalSiteManager` disponible dans le paquet ``zope.component``.
En fait, le gestionnaire global de site est disponible dans un attribut
(``globalSiteManager``) du paquet ``zope.component``. Vous pouvez donc utiliser
directement l'attribut ``zope.component.globalSiteManager``.
Pour inscrire l'adaptateur comme un composant, utilisez la méthode
``registerAdapter`` du registre de composants. Le premier argument doit être
votre classe ou fabrique d'adaptateur. Le deuxième argument est un tuple d'objets
adaptés, c'est à dire les objets sur lesquels vous vous adaptez. Dans cet
exemple, vous vous adaptez seulement à l'objet `IGuest`. Le troisième argument
est l'interface implémentée par le composant adaptateur. Le quatrième argument
est optionnel, il s'agit du nom de cet adaptateur particulier. Si vous donnez un
nom à l'adaptateur, celui devient un `adaptateur nommé`. Si aucun nom n'est
donné, la valeur transmise par défaut est une chaîne vide ('').
Dans l'inscription ci-dessus, vous avez donné l'interface de l'objet adapté et
l'interface fournie par l'adaptateur. Comme vous avez déjà donné ces
informations dans l'implémentation de l'adaptateur, il est inutile de les
spécifier à nouveau. En réalité, vous pouvez effectuer l'inscription de la
manière suivante ::
>>> gsm.registerAdapter(FrontDeskNG, name='ng')
Pour effectuer l'inscription, il existe d'anciennes API que vous devriez
éviter. Les fonctions de l'ancienne API commencent par `provide`, par exemple :
``provideAdapter``, ``provideUtility``, etc. Si vous développez une application
Zope 3, vous pouvez utiliser le langage ZCML (Zope Configuration Markup
Language) pour effectuer les inscriptions des composants. Avec Zope 3, les
composants locaux (persistants) peut être inscrits depuis la ZMI (Zope
Management Interface), ou bien par programmation.
Vous avez inscrit `FrontDeskNG` avec le nom `ng`. De la même manière, vous
pouvez inscrire d'autre adaptateurs avec différents noms. Si un composant est
inscrit sans nom, son nom sera la chaîne vide par défaut.
.. note::
Les composants locaux sont persistants mais les composants globaux sont en
mémoire. Les composants globaux sont inscrits en fonction de la configuration de
l'application. Les composants locaux sont récupérés dans la base de données au
démarrage de l'application.
récupération d'un adaptateur
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
La récupération des composants inscrits dans le registre est effectuée grâce à
deux fonctions disponibles dans le paquet ``zope.component``. La première est
``getAdapter``, la deuxième ``queryAdapter``. Les deux fonctions prennent les
mêmes arguments. ``getAdapter`` lève une exception ``ComponentLookupError`` si
la recherche de composant échoue, tandis que ``queryAdapter`` renvoie `None`.
Vous pouvez importer ces fonctions comme ceci ::
>>> from zope.component import getAdapter
>>> from zope.component import queryAdapter
Dans la section précédente, nous avons inscrit un composant qui fournit
l'interface `IDesk` avec un nom `ng`, et qui s'adapte à l'objet guest.