forked from baijum/zcadoc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
izca-ru.txt
5063 lines (3664 loc) · 152 KB
/
izca-ru.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
==========================================================
Обстоятельное руководство по компонентной архитектуре Zope
==========================================================
:Автор: Baiju M
:Версия: 0.5.8
:Печатный экземпляр на англ. языке: `http:https://www.lulu.com/content/1561045
<http:https://www.lulu.com/content/1561045>`_
:PDF-версия оригинала на англ.: `http:https://www.muthukadan.net/docs/zca.pdf
<http:https://www.muthukadan.net/docs/zca.pdf>`_
:Перевод: Черкашин Е.А.
:Версия перевода: 0.1
Copyright (C) 2007,2008,2009 Baiju M <baiju.m.mail AT gmail.com>.
Разрешается копировать, распространять и/или вносить изменения в этот документ
при соблюдении условий лицензии GNU Free Documentation License, версия 1.2 или
(по вашему желанию) какой-либо поздней версии, официально опубликованной Фондом
свободного программного обеспечения (Free Software Foundation).
Исходный код программ, приведенный в этом документе, распространяется при
соблюдении положений лицензии Zope Public License, версии 2.1 (ZPL).
ИСХСХОДНЫЙ КОД ПРОГРАММ В ЭТОМ ДОКУМЕНТЕ И САМ ДОКУМЕНТ 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:: Благодарности
Много людей помогли мне написать эту книгу. Первоначальный манускрипт
отрецензирован моим коллегой Брэдом Алленом (Brad Allen). Когда я объявил
о книге в моем блоге, общественность вдохновила меня продолжить ее совершенствование.
Кент Тенни (Kent Tenney) принял активное участие в редактировании книги,
кроме того, он переписал основной пример - приложение. Другие присылали
мне замечания и исправления ошибок и недочетов, включая Лоренсо Санчеса
(Lorenzo Gil Sanchez), Михаэля Гаубенвалнера (Michael Haubenwallner),
Нандо Куинтана (Nando Quintana), Стефани Кляйн (Stephane Klein), Тима Кука
(Tim Cook), Камала Джилла (Kamal Gill) и Томаса Герве (Thomas Herve).
Лоренсо перевел книгу на испанский язык, а Стефани на французский.
Огромное всем спасибо!
.. contents::
.. .. sectnum::
Приступая к работе
------------------
Введение
~~~~~~~~
Разработка больших программных систем практически всегда является сложным и
трудоемким занятием, где объектно-ориентированный подход к анализу,
проектированию и реализации (программированию) достаточно хорошо себя
зарекомендовал. Проектирование и разработка программного обеспечения,
основывающиеся на компонентах, становятся популярными в настоящее время.
Компонентный подход и unit-тестирование позволяет достаточно просто
разрабатывать и поддерживать программные системы. Существует много различных
программных инфраструктур (frameworks, фреймворков), реализующих компонентное
проектирование в разных языках и средах программирования, некоторые даже не
зависят от какой-либо конкретной среды разработки. Примерами выступают COM,
разработанный Microsoft, и XPCOM - Mozilla.
**Компонентная архитектура Zope (Zope Component Architecture, ZCA)** - это один
из фреймворков для в среды программирования Python, реализующих компонентный
подход к проектированию и программированию. Он позволяет разрабатывать большие
программные системы в среде программирования Python. И делает это хорошо.
Компонентная архитектура ZCA не требует для своего использования сервера
приложений Zope, ZCA может использоваться в разработке любого Python-приложения.
Справедливо было бы назвать ZCA
`компонентной архитекртуой Python` (Python Component Architecture).
ZCA позволяет использовать объекты Python эффективно. `Компоненты` - это
объекты, которые легко использовать повторно, а к их интерфейсам можно получить
полный доступ во время исполнения программы (introspection). `Интерфейс` - это
объект, который описывает как следует взаимодействовать с конкретной
компонентой. Другими словами, компонента `обеспечивает сервисы` (provide),
определенные в интерфейсе. Компонента `реализуется` (implemented) в виде класса
(class) или другого call-объекта (callable object). И не важно, как реализована
эта компонента, а вот что важно - это то, что она соответствует `требованиям`,
определенным в интерфейсе (interface contract). При помощи ZCA сложность системы
распределяется между множеством кооперирующихся компонент. Механизм такой
кооперации обеспечивается двумя базовыми разновидностями компонент в ZCA:
`адаптером` (adapter) и `утилитой` (utility).
ZCA распределен между тремя базовыми пакетами:
- ``zope.interface``, используемый для определения (задания) интерфейсов
компонент.
- ``zope.event``, обеспечивающего поддержку простого механизма инициирования и
обработки событий.
- ``zope.component``, обеспечивающего создание (генерирование) и регистрацию
компонент, а также их извлечение по запросу.
Обращаю внимание, что ZCA сам по себе не является компонентой и не состоит из
них, ZCA скорее механизм и библиотека для создания, регистрации и обеспечения
доступа к зарегистрированным компонентам. В продолжение к предыдущему замечанию,
`Адаптер` - это, как правило, обычный класс Python (или фабрика классов
(factory), в общем случае), а `утилита` - это обычный call-объект в среде
исполнения Python.
Фреймворк ZCA разрабатывается как одно из направлений развития проекта Zope 3.
Как сказано выше ZCA является чисто питоновским фреймворком, и может
использоваться в любых приложениях Python. В настоящее время ZCA активно
используется в проектах Zope 3, Zope 2 и Grok. Существуют и другие приложения, в
т.ч. и неинтернет приложения, использующие ZCMA [#projects]_.
.. [#projects] http:https://wiki.zope.org/zope3/ComponentArchitecture
Краткая история проекта
~~~~~~~~~~~~~~~~~~~~~~~
Проект ZCA начался в 2001 году и был частью проекта Zope 3. Причиной появления
проекта послужил анализ опыта, полученного при разработке комплексных
программных систем на основе Zope 2. Джим Фултон (Jim Fulton) стал лидером
проекта. Много разработчиков внесли свой вклад в дизайн и реализацию ZCA: Стефан
Рихтер (Stephan Richter), Филипп фон Вайтершаусен (Philipp von Weitershausen),
Гвидо ван Россум (Guido van Rossum), известный также как *Python BDFL*, Трес
Сивер (Tres Seaver), Филипп Дж Эби (Phillip J Eby) и Мартин Фаассен (and Martijn
Faassen) и др.
В самом начале в ZCA были определены дополнительные компоненты - `сервисы`
(services) и `представления` (views), но в процессе разработки оказалось, что
`утилиты` заменяют полностью сервисы, а `мультиадаптеры` - представления. В
настоящее время ZCA включает в себя совсем небольшое количество базовых
разновидностей компонент: `утилиты`, `адаптеры`, `подписчики` (абоненты)
(subscribers) и `обработчики` (handlers). На самом деле `подписчики` и
`обработчики` - это два особых вида адаптеров.
В процессе подготовки версии Zope 3.2 Джим Фултон предложил значительно
упростить фреймворк ZCA [#proposal]_. В результате этого упрощения появился
новый общий интерфейс `IComponentRegistry`, позволяющий регистрировать как
локальные так и глобальные компоненты.
.. [#proposal] http:https://wiki.zope.org/zope3/LocalComponentManagementSimplification
В результате от пакета ``zope.component`` перестал зависеть достаточно длинный
список других пакетов, при этом оставшиеся зависимости не относились напрямую к
разработке приложений Zope 3. На конференции PyCon 2007 Джим Фултон предложил
включить [в пакет], используемое в пакете setuptools [фичу] `extras_require`,
чтобы дополнительно изолировать базовый набор функций ZCA от дополнительных
(add-on) [фич] [#extras]_.
.. [#extras] http:https://peak.telecommunity.com/DevCenter/setuptools#declaring-dependencies
В марте 2009, Трес Сивер окончательно устранил зависимости пакета от
``zope.deferredimport`` и ``zope.proxy``.
Теперь проект ZCA является независимым проектом со своим собственным планом
[релизов] и хранилищем (репозиторием) Subversion. Проект как и прежде является
составляющей частью фреймфорка Zope [#framework]_. При этом ошибки и недочеты,
выявляемые в ZCA, все еще публикуются на сайте системы отслеживания ошибок
проекта Zope 3 [#bugs]_. Кроме того, основной список рассылки `zope-dev` все еще
используется для информационного обмена разработчиками ZCA [#discussions]_. Есть
еще другой список рассылки, предназначенный для пользователей Zope 3,
`zope3-users`, в котором можно получить ответы на вопросы относительно
фреймворка ZCA [#z3users]_.
.. [#framework] http:https://docs.zope.org/zopeframework/
.. [#bugs] https://bugs.launchpad.net/zope3
.. [#discussions] http:https://mail.zope.org/mailman/listinfo/zope-dev
.. [#z3users] http:https://mail.zope.org/mailman/listinfo/zope3-users
Установка
~~~~~~~~~
Пакеты ``zope.component``, ``zope.interface`` и
``zope.event`` являются ядром Компонентной архитектуры Zope. Они обеспечивают
технические средства для определения, регистрации и поиска компонент. Пакет
``zope.component`` и пакеты, зависящие от него, доступны в формате egg на сайте
Python Package Index (PyPI) [#pypi]_.
.. [#pypi] Repository of Python packages: http:https://pypi.python.org/pypi
Лучше всего устанавливать ``zope.component`` и его зависимые пакеты, используя
`easy_install` [#easyinstall]_ ::
$ easy_install zope.component
.. [#easyinstall] http:https://peak.telecommunity.com/DevCenter/EasyInstall
Эта команда загружает ``zope.component`` и зависимые пакеты с сайта PyPI и
устанавливает все как библиотеки интерпретатора Python.
С другой стороны можно загрузить ``zope.component`` и зависящие от него пакеты с
сайта PyPI и, затем, самостоятельно установить их. Установку пакетов следует
производить в порядке, описанном далее. В операционной системе Windows
необходимо загрузить двоичные (binary) сборки следующих пакетов.
1. ``zope.interface``
2. ``zope.event``
3. ``zope.component``
Для установки загруженных пакетов можно, как и раньше, использовать
команду ``easy_install``, при этом передавая в качестве параметра
устанавливаемые пакеты, аналогично двоичным egg-пакетам. (Можно передать в
качестве параметров все пакеты одновременно в одной командной строке.)::
$ 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
Кроме того, все пакеты можно разархивировать и установить по одному, например::
$ 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
Эта последовательность позволяет устанавливать ZCA как общесистемную библиотеку
Python в директорий (папку) ``site-packages``, что, иногда, создает некоторые
проблемы. Джим Фултон в списке рассылки Zope 3 не рекомендовал устанавливать
пакеты в общесистемной библиотеке
Python [#systempython]_. Рекомендуется устанавливать виртуальное окружение при
помощи программы ``virtualenv`` и/или пакета
``zc.buildout``, что создает больше возможностей для экспериментов с пакетами
Python. Кроме того, в виртуальном окружении удобно развертывать готовые
приложения (deployments).
.. [#systempython] http:https://article.gmane.org/gmane.comp.web.zope.zope3/21045
Подготовка виртуального окружения
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Существует множество популярных подходов к настройке изолированного виртуального
окружения для среды программирования Python, удобного для разработки приложений.
Наиболее популярные — это пакет ``virtualenv``, разработанный Яном Бикингом
(Ian Biking) и
пакет ``zc.buildout``, разработанный Джимом Фултоном. Оба этих пакета можно
использовать одновременно.
**virtualenv**
Пакет ``virtualenv`` устанавливается при помощи команды ``easy_install``::
$ easy_install virtualenv
Затем создается новое окружение::
$ virtualenv --no-site-packages myve
Команда создаст новое окружение в директории ``myve``.
Теперь в директории ``myve`` можно установить
``zope.component`` и зависимые пакеты при помощи команды ``easy_install``, если
запустить ее в директории
``myve/bin``::
$ cd myve
$ ./bin/easy_install zope.component
Теперь можно импортировать пакеты ``zope.interface`` и ``zope.component`` из
интерпретатора ``python``, который находится в директории ``myve/bin``::
$ ./bin/python
После запуска Python покажет обычное приглашение (prompt) для ввода команд и
выражений, в котором можно выполнять приводимый далее в книге программный код.
**zc.buildout**
Используя пакет ``zc.buildout`` и `рецепт` (recipe) ``zc.recipe.egg`` можно
создавать специализированные версии интерпретатора
Python с встроенными egg-пакетами. Для этого необходимо установить пакет
``zc.buildout`` при помощи команды ``easy_install``, что также можно сделать
внутри виртуального окружения.
Чтобы создать новое buildout-окружение для проведения экспериментов с
egg-пакетами Python, сначала создайте для него новый директорий, и, затем,
создайте в нем собственно buildout-окружение его при помощи команды
``buildout init``::
$ mkdir mybuildout
$ cd mybuildout
$ buildout init
Теперь директорий ``mybuildout`` новое buildout-окружение. Настройки окружения
по умолчанию хранятся в файле `buildout.cfg` . В самом начале
в нем будет содержаться следующий текст::
[buildout]
parts =
Надо внести в настройки следующие изменения::
[buildout]
parts = py
[py]
recipe = zc.recipe.egg
interpreter = python
eggs = zope.component
Запуск команды ``buildout`` из директория ``mybuildout/bin`` (без аргументов)
создаст новый интерпретатор Python в директории
``mybuildout/bin``::
$ ./bin/buildout
$ ./bin/python
Последняя команда создаст приглашение Python, в котором можно запускать
приводимый далее в книге программный код.
Пример задачи
-------------
Введение
~~~~~~~~
Рассмотрим приложение, предназначенное для регистрации постояльцев в гостинице.
В среде Python его можно реализовать множеством способов.
Сначала кратко рассмотрим процедурный подход, затем перейдем к
объектно-ориентированному программированию. После анализа результатов дизайна и
реализации задачи при помощи объектно-ориентированного подхода, рассмотрим как
данная задача решается при помощи классических шаблонов проектирования
(паттернов проектирования, design patterns) `адаптер` и `интерфейс`. Таким
образом появится начальное представление о возможностях Компонентой архитектуры
Zope.
Процедурный подход
~~~~~~~~~~~~~~~~~~
Система храненияя данных - очень важный момент в рпзработке приложений.
Будем использовать в рассматриваемом примере словарь (dictionary) языка Python в
качестве такой системы хранения данных.
Каждой записи соответствует сгенерированный уникальный ключ (ID).
Ключ ассоциируется со значением, которое будет словарем, содержащим
данные о бронировании комнаты в гостинице.
>>> bookings_db = {} #key - уникальный идентификатор (ID), value - данные брони
В реализации системы будет, как минимум, функция, которой передаются
данные о бронировании, и функция, которая генерирует уникальное
значение ID ключа для ассоциации с данными брони.
Генерировать значения ключа можно следующим образом::
>>> 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
Как видно из программы, реализация функции `get_next_id` достаточно простая.
Функция получает из словаря список ключей. Если список пуст, то это значит,
что произошло первое бронирование комнаты в гостинице,
функция возвращает `1`. Если список не пуст, то добавить `1` к
максмальному значению ключей и вернуть полученное значение.
Теперь созданную выше функцию встроим в программы код, создающий
записи в словаре bookings_db::
>>> def book_room(name, place):
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': name,
... 'room': place
... }
В хранилище необходимо учесть, что может потребоваться хранить
другие данные, такие как нижеперечисленные:
- номера телефонов,
- особенности бронирования,
- способы платежей клиентов,
- ...
Необходимо реализовать следующие функции:
- отмена бронирования,
- уточнение бронирования,
- оплата проживания,
- постоянное хранение данных и доступ к ним,
- обеспечение хащиты информации в хранилище,
- ...
Процедурная реализация данного примера требует создания большого
числа функций, обменивающихся информацией друг с другом. Изменения
требований к дизайну и реализации, добавление новых функций, создает
условия к усложнению процесса поддержки программного кода.
Кроме того, становится сложнее выявлять и исправлять ошибки.
На этом закончим обсуждение процедурного подхода. Обеспечивать
постоянное хранение данных, гибкость дизайна и
возможность тестирования программного кода, будет проще,
используя объекты.
Объектно-ориентированный подход
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. ??? should this paragraph talk about "creating an object for
handling registration" or "creating a class to handle registration"?
Our discussion of object oriented design will introduce the `class` which
serves to encapsulate the data, and the code to manage it.
Our main class will be the ``FrontDesk``. ``FrontDesk``, or other
classes it delegates to, will know how to manage the data for the
hotel. We will create `instances` of ``FrontDesk`` to apply this
knowledge to the business of running a hotel.
Experience has shown that by consolidating the code and data
requirements via objects, we will end up with a design which is
easier to understand, test, and change.
Lets look at the implementation details of a ``FrontDesk`` class::
>>> class FrontDesk(object):
...
... def book_room(self, name, place):
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': name,
... 'place': place
... }
In this implementation, the `frontdesk` object (an instance of
`FrontDesk` class) is able to handle the bookings. We can
use it like this::
>>> frontdesk = FrontDesk()
>>> frontdesk.book_room("Jack", "Bangalore")
Any real project will involve changing requirements. In this
case management has decided that each guest must provide a phone
number, so we must change the code.
We can achieve this requirement by adding one argument to the
`book_room` method which will be added to the dictionary of values::
>>> 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
... }
In addition to migrating the data to new schema, we now have to
change all calls to ``FrontDesk``. If we abstract the details of
guest into an object and use it for registration, the code changes
can be minimized. We now can make changes to the details of the
guest object and the calls to ``FrontDesk`` won't need to change.
Now we have::
>>> 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
... }
We still will have to change code to respond to changing requirements.
This is unavoidable, however, our goal is to minimize those changes,
thereby increasing maintainability.
.. note::
When coding, it is important to feel free to make changes without
fear of breaking the application. The way to get the immediate
feedback required is via automated testing. With well written
tests (and good version control) you can make changes large or
small with impunity. A good source of information about this
programming philosophy is the book `Extreme Programming Explained`
by Kent Beck.
By introducing the guest object, you saved some typing. More
importantly, the abstraction provided by the guest object made the
system simpler and more understandable. As a result, the code is
easier to restructure and maintain.
The adapter pattern
~~~~~~~~~~~~~~~~~~~
In a real application, the frontdesk object will need to handle
functionalities such as cancellations and updates. In the current
design, we will need to pass the guest object to frontdesk every time
we call methods such as `cancel_booking` and `update_booking`.
We can avoid this requirement if we pass the guest object to
FrontDesk.__init__(), making it an attribute of the 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 ...
.. include this bit at the front of the `Adapters` section when I get
the equivalent quote from the Patterns book to start the
`Interfaces` section
The solution we have reached is a common design pattern called,
`Adapter`. The `Gang of Four` [#patternbook]_ give this as the
*intent* of Adapter::
"Convert the interface of a class into another interface clients
expect. Adapter lets classes work together that couldn't otherwise
because of incompatible interfaces."
The solution we have reached is a well known pattern, the *adapter*.
In general, an adapter *contains* an *adaptee*::
>>> class Adapter(object):
...
... def __init__(self, adaptee):
... self.adaptee = adaptee
This pattern will be useful in dealing with implementation details
which depend on considerations such as:
- changing customer requirements
- storage requirements (ZODB, RDBM, XML ...)
- output requirements (HTML, PDF, plain text ...)
- markup rendering (ReST, Markdown, Textile ...)
ZCA uses adapters and a *component registry* to provide the capability
to change implementation details of code via *configuration*.
As we will see in the section on ZCA adapters, the ability to
configure implementation details provides useful capability:
- the ability to switch between implementations
- the ability to add implementations as needed
- increased re-use of both legacy and ZCA code
These capabilities lead to code that is flexible, scalable and
re-usable. There is a cost however, maintaining the component registry
adds a level of complexity to the application. If an application will
never require these features, ZCA is unnecessary.
We are now ready to begin our study of the Zope Component
Architecture, beginning with interfaces.
Interfaces
----------
Introduction
~~~~~~~~~~~~
The README.txt [#readmes]_ in path/to/zope/interface defines
interfaces like this::
Interfaces are objects that specify (document) the external behavior
of objects that "provide" them. An interface specifies behavior
through:
- Informal documentation in a doc string
- Attribute definitions
- Invariants, which are conditions that must hold for objects that
provide the interface
The classic software engineering book `Design Patterns` [#patternbook]_
by the `Gang of Four` recommends that you "Program to an interface,
not an implementation". Defining a formal interface is helpful in
understanding a system. Moreover, interfaces bring to you all the
benefits of ZCA.
.. [#readmes] The Zope code tree is full of README.txt files which
offer wonderful documentation.
.. [#patternbook] http:https://en.wikipedia.org/wiki/Design_Patterns
An interface specifies the characteristics of an object, it's
behaviour, it's capabilities. The interface describes *what* an
object can do, to learn *how*, you must look at the implementation.
Commonly used metaphors for interfaces are `contract` or `blueprint`,
the legal and architectural terms for a set of specifications.
In some modern programming languages: Java, C#, VB.NET etc, interfaces
are an explicit aspect of the language. Since Python lacks
interfaces, ZCA implements them as a meta-class to inherit from.
Here is a classic *hello world* style example::
>>> class Host(object):
...
... def goodmorning(self, name):
... """Say good morning to guests"""
...
... return "Good morning, %s!" % name
In the above class, you defined a `goodmorning` method. If you call
the `goodmorning` method from an object created using this class, it
will return `Good morning, ...!` ::
>>> host = Host()
>>> host.goodmorning('Jack')
'Good morning, Jack!'
Here ``host`` is the actual object your code uses. If you want to
examine implementation details you need to access the class ``Host``,
either via the source code or an API [#api]_ documentation tool.
.. [#api] http:https://en.wikipedia.org/wiki/Application_programming_interface
Now we will begin to use the ZCA interfaces. For the class given
above you can specify the interface like this::
>>> from zope.interface import Interface
>>> class IHost(Interface):
...
... def goodmorning(guest):
... """Say good morning to guest"""
As you can see, the interface inherits from zope.interface.Interface.
This use (abuse?) of Python's class statement is how ZCA defines an
interface. The ``I`` prefix for the interface name is a useful
convention.
Declaring interfaces
~~~~~~~~~~~~~~~~~~~~
You have already seen how to declare an interface using
``zope.interface`` in previous section. This section will explain the
concepts in detail.
Consider this example 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"""
The interface, ``IHost`` has two attributes, ``name`` and
``goodmorning``. Recall that, at least in Python, methods are also
attributes of classes. The ``name`` attribute is defined using
``zope.interface.Attribute`` class. When you add the attribute
``name`` to the ``IHost`` interface, you don't set an initial value.
The purpose of defining the attribute ``name`` here is merely to
indicate that any implementation of this interface will feature an
attribute named ``name``. In this case, you don't even say what type
of attribute it has to be!. You can pass a documentation string as a
first argument to ``Attribute``.
The other attribute, ``goodmorning`` is a method defined using a
function definition. Note that `self` is not required in interfaces,
because `self` is an implementation detail of class. For example, a
module can implement this interface. If a module implement this
interface, there will be a ``name`` attribute and ``goodmorning``
function defined. And the ``goodmorning`` function will accept one
argument.
Now you will see how to connect `interface-class-object`. So object
is the real living thing, objects are instances of classes. And
interface is the actual definition of the object, so classes are just
the implementation details. This is why you should program to an
interface and not to an implementation.
Now you should familiarize two more terms to understand other
concepts. First one is `provide` and the other one is `implement`.
Object provides interfaces and classes implement interfaces. In other
words, objects provide interfaces that their classes implement. In
the above example ``host`` (object) provides ``IHost`` (interface) and
``Host`` (class) implement ``IHost`` (interface). One object can
provide more than one interface also one class can implement more than
one interface. Objects can also provide interfaces directly, in
addition to what their classes implement.
.. note::
Classes are the implementation details of objects. In Python,
classes are callable objects, so why other callable objects can't
implement an interface. Yes, it is possible. For any `callable
object` you can declare that it produces objects that provide some
interfaces by saying that the `callable object` implements the
interfaces. The `callable objects` are generally called as
`factories`. Since functions are callable objects, a function can
be an `implementer` of an interface.
Implementing interfaces
~~~~~~~~~~~~~~~~~~~~~~~
To declare a class implements a particular interface, use the function
``zope.interface.implements`` in the class statement.
Consider this example, here ``Host`` implements ``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::
If you wonder how ``implements`` function works, refer the blog post
by James Henstridge
(http:https://blogs.gnome.org/jamesh/2005/09/08/python-class-advisors/) .
In the adapter section, you will see an ``adapts`` function, it is
also working similarly.
Since ``Host`` implements ``IHost``, instances of ``Host`` provides
``IHost``. There are some utility methods to introspect the
declarations. The declaration can write outside the class also. If
you don't write ``interface.implements(IHost)`` in the above example,
then after defining the class statement, you can write like this::
>>> from zope.interface import classImplements
>>> classImplements(Host, IHost)
Example revisited
~~~~~~~~~~~~~~~~~
Now, return to the example application. Here you will see how to
define the interface of the frontdesk object::
>>> from zope.interface import Interface
>>> class IDesk(Interface):
... """A frontdesk will register object's details"""
...
... def register():
... """Register object's details"""
...
Here, first you imported ``Interface`` class from ``zope.interface``
module. If you define a subclass of this ``Interface`` class, it
will be an interface from Zope Component Architecture point of view.
An interface can be implemented, as you have already seen, in a class
or any other callable object.
The frontdesk interface defined here is ``IDesk``. The documentation
string for interface gives an idea about the object. By defining a
method in the interface, you made a contract for the component, that
there will be a method with same name available. For the method
definition interface, the first argument should not be `self`,
because an interface will never be instantiated nor will its methods
ever be called. Instead, the interface class merely documents what
methods and attributes should appear in any normal class that claims
to implement it, and the `self` parameter is an implementation detail
which doesn't need to be documented.
As you know, an interface can also specify normal attributes::
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> class IGuest(Interface):
...
... name = Attribute("Name of guest")
... place = Attribute("Place of guest")
In this interface, guest object has two attributes specified with
documentation. An interface can also specify both attributes and
methods together. An interface can be implemented in a class, module
or any other objects. For example a function can dynamically create
the component and return, in this case the function is an implementer
for the interface.
Now you know what is an interface and how to define and use it. In
the next chapter you can see how an interface is used to define an
adapter component.
Marker interfaces
~~~~~~~~~~~~~~~~~
An interface can be used to declare that a particular object belongs
to a special type. An interface without any attribute or method is
called `marker interface`.
Here is a `marker interface`::
>>> from zope.interface import Interface
>>> class ISpecialGuest(Interface):
... """A special guest"""
This interface can be used to declare an object is a special guest.
Invariants
~~~~~~~~~~
Sometimes you will be required to use some rule for your component
which involve one or more normal attributes. These kind of rule is
called `invariants`. You can use ``zope.interface.invariant`` for
setting `invariants` for your objects in their interface.
Consider a simple example, there is a `person` object. A person
object has `name`, `email` and `phone` attributes. How do you
implement a validation rule that says either email or phone have to
exist, but not necessarily both.
First you have to make a callable object, either a simple function or
callable instance of a class like this::
>>> def contacts_invariant(obj):
...
... if not (obj.email or obj.phone):
... raise Exception(
... "At least one contact info is required")
Then define the `person` object's interface like this. Use the
``zope.interface.invariant`` function to set the 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)
Now use `validateInvariants` method of the interface to validate::
>>> from zope.interface import implements
>>> class Person(object):
... implements(IPerson)
...
... name = None
... email = None
... phone = None
>>> jack = Person()
>>> jack.email = u"[email protected]"
>>> IPerson.validateInvariants(jack)
>>> jill = Person()
>>> IPerson.validateInvariants(jill)
Traceback (most recent call last):
...
Exception: At least one contact info is required
As you can see `jack` object validated without raising any exception.
But `jill` object didn't validated the invariant constraint, so it
raised exception.
Adapters
--------
Implementation
~~~~~~~~~~~~~~
This section will describe adapters in detail. Zope Component
Architecture, as you noted, helps to effectively use Python objects.
Adapter components are one of the basic components used by Zope
Component Architecture for effectively using Python objects. Adapter
components are Python objects, but with well defined interface.
To declare a class is an adapter use `adapts` function defined in
``zope.component`` package. Here is a new `FrontDeskNG` adapter
with explicit interface declaration::
>>> 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
... }
What you defined here is an `adapter` for `IDesk`, which adapts
`IGuest` object. The `IDesk` interface is implemented by
`FrontDeskNG` class. So, an instance of this class will provide
`IDesk` interface.
::
>>> 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
The `FrontDeskNG` is just one adapter you created, you can also
create other adapters which handles guest registration differently.
Registration
~~~~~~~~~~~~
To use this adapter component, you have to register this in a
component registry also known as site manager. A site manager
normally resides in a site. A site and site manager will be more
important when developing a Zope 3 application. For now you only
required to bother about global site and global site manager ( or
component registry). A global site manager will be in memory, but a
local site manager is persistent.
To register your component, first get the global site manager::
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(FrontDeskNG,
... (IGuest,), IDesk, 'ng')
To get the global site manager, you have to call
``getGlobalSiteManager`` function available in ``zope.component``
package. In fact, the global site manager is available as an
attribute (``globalSiteManager``) of ``zope.component`` package. So,
you can directly use ``zope.component.globalSiteManager`` attribute.
To register the adapter in component, as you can see above, use
``registerAdapter`` method of component registry. The first argument
should be your adapter class/factory. The second argument is a tuple
of `adaptee` objects, i.e, the object which you are adapting. In this
example, you are adapting only `IGuest` object. The third argument is
the interface implemented by the adapter component. The fourth
argument is optional, that is the name of the particular adapter.
Since you gave a name for this adapter, this is a `named adapter`. If
name is not given, it will default to an empty string ('').
In the above registration, you have given the adaptee interface and
interface to be provided by the adapter. Since you have already given
these details in adapter implementation, it is not required to specify
again. In fact, you could have done the registration like this::
>>> gsm.registerAdapter(FrontDeskNG, name='ng')
There are some old API to do the registration, which you should avoid.
The old API functions starts with `provide`, eg: ``provideAdapter``,