-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathizca-es.txt
4242 lines (3081 loc) · 121 KB
/
izca-es.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
===================================
Arquitectura de Componentes de Zope
===================================
:Autor: Baiju M
:Version: 0.4.2
:URL original: `http://www.muthukadan.net/docs/zca.pdf
<http://www.muthukadan.net/docs/zca.pdf>`_
:Traductor: Lorenzo Gil Sanchez <lgs@sicem.biz>
:URL en español: `http://www.muthukadan.net/docs/zca-es.pdf
<http://www.muthukadan.net/docs/zca-es.pdf>`_
Copyright (C) 2007 Baiju M <baiju.m.mail AT gmail.com>.
Se permite la copia, distribución y/o modificación de este documento
bajo los términos de la Licencia de Documentación Libre GNU, Versión
1.2 o (si lo prefiere) cualquier otra versión posterior publicada por
la Free Software Foundation.
El código fuente en este documento está sujeto a la Licencia
Pública Zope, Versión 2.1 (ZPL).
EL PROGRAMA INCLUIDO EN ESTE DOCUMENTO SE OFRECE "TAL CUAL"
SIN GARANTÍA DE NINGÚN TIPO, YA SEA EXPLÍCITA O IMPLÍCITA, INCLUYENDO,
PERO SIN LIMITARSE A, LAS GARANTÍAS IMPLÍCITAS MERCANTILES Y DE
APTITUD PARA UN PROPÓSITO DETERMINADO.
.. note::
Gracias a Kent Tenney (Wisconsin, USA) y Brad Allen (Dallas, USA)
por sus sugerencias.
.. contents::
.. .. sectnum::
Primeros pasos
--------------
Introducción
~~~~~~~~~~~~
Desarrollar un sistema software grande es siempre muy complicado. Se
ha visto que un enfoque orientado a objetos para el análisis, diseño
y programación funciona bien al tratar con sistemas grandes. El diseño
basado en componentes, y la programación utilizando componentes se
están haciendo muy populares últimamente. Hay muchos marcos de trabajo
que soportan el diseño basado en componentes en diferentes lenguajes,
algunos incluso son neutrales con respecto al lenguaje. Ejemplos de
esto son el COM de Microsoft y el XPCOM de Mozilla.
La Arquitectura de Componentes de Zope (ZCA) es un marco de trabajo
en Python que soporta el diseño y la programación basada en componentes.
La ZCA funciona muy bien al desarrollar sistemas de software grandes en
Python. La ZCA no es específica al servidor de aplicaciones Zope, se
puede utilizar para desarrollar cualquier aplicación Python. Quizás
debería llamarse la `Arquitectura de Componentes de Python`.
Hay dos paquetes principales relacionados con la arquitectura de
componentes de Zope:
- ``zope.interface`` utilizado para definir la interfaz de un
componente.
- ``zope.component`` se encarga de registrar y recuperar
componentes.
El objetivo fundamental de la arquitectura de componentes de Zope es
utilizar objetos Python de forma eficiente. Los componentes son objetos
reusables con introspección para sus interfaces. Un componente provee
una interfaz implementada en una clase, o cualquier objeto llamable.
No importa cómo se implemente el componente, lo que importa es
que cumpla los contratos definidos en su interfaz. Utilizando la
arquitectura de componentes de Zope puedes distribuir la complejidad
de sistemas entre varios componentes cooperantes. La arquitectura de
componentes de Zope te ayuda a crear dos tipos básicos de componentes:
`adaptador` y `utilidad`.
Recuerda, la ZCA no trata sobre los componentes en sí mismo, sino sobre
la creación, registro y recuperación de los componentes. Recuerda
también, un `adaptador` es una clase Python normal (o una fábrica en
general) y una `utilidad` es un objeto llamable Python normal.
El marco de trabajo de la ZCA se desarrolla como parte del proyecto
Zope 3. La ZCA, como ya se ha mencionado, es un marco de trabajo
puramente Python, por tanto se puede utilizar en cualquier tipo de
aplicación Python. Actualmente ambos proyectos Zope 3 y Zope 2 utilizan
este marco de trabajo extensívamente. Hay otros muchos proyectos
incluyendo aplicaciones no web que utilizan la Arquitectura de
Componentes de Zope [#projects]_.
Una breve historia
~~~~~~~~~~~~~~~~~~
El proyecto del marco de trabajo ZCA comenzó en 2001 como parte del
proyecto Zope 3. Fue tomando forma a partir de las lecciones aprendidas
al desarrollar sistemas software grandes usando Zope 2. Jim Fulton fue
el jefe de proyecto de este proyecto. Mucha gente contribuyó al diseño
y a la implementación, incluyendo pero sin limitarse a, Stephan
Richter, Philipp von Weitershausen, Guido van Rossum (también conocido
como *Python BDFL*), Tres Seaver, Phillip J Eby y Martijn Faassen.
Inicialmente la ZCA definía componentes adicionales; `servicios` y
`vistas`, pero los desarrolladores se dieron cuenta de que la utilidad
podía sustituir `servicio` y el multi-adaptador podía sustituir `view`.
Ahora la ZCA tiene un número muy pequeño de tipos de componentes
principales: utilidades, adaptadores, subscriptores y manejadores. En
realidad, subscriptores y manejadores son dos tipos especiales de
adaptadores.
Durante el ciclo de la versión Zope 3.2, Jim Fulton propuso una gran
simplificación de la ZCA [#proposal]_. Con esta simplificación se creó
una nueva interfaz única (`IComponentRegistry`) para registrar
componentes locales y globales.
El paquete ``zope.component`` tenía una larga lista de dependencias,
muchas de las cuales no eran necesarias para una aplicación no Zope 3.
Durante la PyCon 2007, Jim Fulton añadió la característica
``extras_require`` de setuptools para permitir la separación de la
funcionalidad básica de la ZCA de las características adicionales
[#extras]_.
Hoy el proyecto de la ZCA es un proyecto independiente con su propio
ciclo de versiones y su repositorio Subversion. Sin embargo, los
problemas y los errores aún se controlan como parte del proyecto
Zope 3 [#bugs]_, y la lista principal zope-dev se utiliza para los
debates de desarrollo [#discussions]_.
Instalación
~~~~~~~~~~~
El paquete ``zope.component``, junto con el paquete ``zope.interface``
son el núcleo de la arquitectura de componentes Zope. Ofrecen
facilidades para definir, registrar y buscar componentes. El paquete
``zope.component`` y sus dependencias están disponibles en formato
egg (huevo) desde el Índice de Paquetes Python (PyPI) [#pypi]_.
Puedes instalar ``zope.component`` y sus dependencias utilizando
`easy_install` [#easyinstall]_ ::
$ easy_install zope.component
Este comando descargará ``zope.component`` y sus dependencias desde
PyPI y los instalará en tu ruta Python.
Alternativamente, puedes descargar ``zope.component`` y sus
dependencias desde PyPI y luego instalarlos. Instala los paquetes en
el siguiente orden. En Windows, puede que necesitas los paquetes
binarios de ``zope.interface`` y ``zope.proxy``.
1. ``zope.interface``
2. ``zope.proxy``
3. ``zope.deferredimport``
4. ``zope.event``
5. ``zope.deprecation``
6. ``zope.component``
Para instalar estos paquetes, después de haberlos descargados, puedes
utilizar el comando ``easy_install`` con los huevos como argumento.
(También puedes darle todos estos huevos como argumneto en la misma
linea.)::
$ easy_install /path/to/zope.interface-3.4.x.tar.gz
$ easy_install /path/to/zope.proxy-3.4.x.tar.gz
...
Estos métodos instalan la ZCA en el `Python de sistema`, en el
directorio ``site-packages``, lo cual puede causar problemas. En
un mensaje a la lista de correo, Jim Fulton recomienda que no se
utilice el Python de sistema [#systempython]_.
Experimentando con código
~~~~~~~~~~~~~~~~~~~~~~~~~
``virtualenv`` ay ``zc.buildout`` son herramientas que instalan la
ZCA en un entorno de trabajo aislado. Esto es una buena práctica
para experimentar con código y el estar familiarizado con estas
herramientas será beneficioso para desarrollar e implantar
aplicaciones.
Puedes instalar ``virtualenv`` usando ``easy_install``::
$ easy_install virtualenv
Ahora crea un nuevo entorno así::
$ virtualenv miev
Esto creará un nuevo entorno virtual en el directorio ``miev``.
Ahora, desde dentro del directorio ``miev``, puedes instalar
``zope.component`` y sus dependencias utilizando el ``easy_install``
que hay dentro del directorio ``miev/bin``::
$ cd miev
$ ./bin/easy_install zope.component
Ahora puedes importar ``zope.interface`` y ``zope.component`` desde
el nuevo intérprete ``python`` dentro del directorio ``miev/bin``::
$ ./bin/python
Este comando ejecutará un intérprete de Python que puedes usar
para ejecutar el código de este libro.
Utilizando ``zc.buildout`` con la receta ``zc.recipe.egg`` se
puede crear un intérprete de Python con los huevos Python especificados.
Primero instala ``zc.buildout`` usando el comando ``easy_install``.
(Puedes hacerlo también dentro de un entorno virtual). Para crear un
nuevo buildout para experimentar con huevos Python, primero crea un
directorio e inicialízalo usando el comando ``buildout init``::
$ mkdir mibuildout
$ cd mibuildout
$ buildout init
Ahora el nuevo directorio ``mibuildout`` es un buildout. El archivo
de configuración predeterminado de buildout es `buildout.cfg` . Después
de la inicialización, tendrá el siguiente contenido::
[buildout]
parts =
Puedes cambiarlo a::
[buildout]
parts = py
[py]
recipe = zc.recipe.egg
interpreter = python
eggs = zope.component
Ahora ejecuta el comando ``buildout`` disponible dentro del directorio
``mibuildout/bin`` sin ningún argumento. Esto creará un nuevo intérprete
Python dentro del directorio ``mibuildout/bin``::
$ ./bin/buildout
$ ./bin/python
Este comando ejecutará un intérprete de Python que puedes usar
para ejecutar el código de este libro.
.. [#projects] http://wiki.zope.org/zope3/ComponentArchitecture
.. [#proposal] http://wiki.zope.org/zope3/LocalComponentManagementSimplification
.. [#extras] http://peak.telecommunity.com/DevCenter/setuptools#declaring-dependencies
.. [#bugs] https://bugs.launchpad.net/zope3
.. [#discussions] http://mail.zope.org/mailman/listinfo/zope-dev
.. [#pypi] Repository of Python packages: http://pypi.python.org/pypi
.. [#easyinstall] http://peak.telecommunity.com/DevCenter/EasyInstall
.. [#systempython] http://article.gmane.org/gmane.comp.web.zope.zope3/21045
Un ejemplo
---------
Introducción
~~~~~~~~~~~~
Considera una aplicación de gestión para registrar los huéspedes que se
hospedan en un hotel. Python puede implementar esto de varias formas
distintas. Empezaremos con un mirada breve a un enfoque procedural, y
después cambiaremos a un enfoque orientado a objetos básico. Mientras
examinamos el enfoque orientado a objetos, veremos como como podemos
beneficiarnos de los patrones de diseño clásicos, `adaptador` e
`interface`. Esto nos llevará al mundo de la Arquitectura de Componentes
de Zope.
Enfoque procedural
~~~~~~~~~~~~~~~~~~
En una aplicación de gestión, el almacenamiento de los datos es muy
importante. Por simplicidad, este ejemplo utilizará un diccionario
Python como almacenamiento. Las claves del diccionario serán
identificadores únicos para un huesped en particular. Y el valor
será otro diccionario cuyas claves son los nombres de las propiedades::
>>> huespedes_db = {} #clave: id único, valor: detalles en un diccionario
En un método simplista, una función que acepte detalles como argumentos
es suficiente para hacer el registro. También necesitas una función
auxiliar para obtener el próximo identificador de tu almacenamiento de
datos.
Esta función auxiliar, para obtener el próximo identificador se puede
implementar así::
>>> def proximo_id():
... claves = huespedes_db.keys()
... if claves == []:
... proximo = 1
... else:
... proximo = max(claves) + 1
... return proximo
Como puedes ver, la implementación de la función `proximo_id` es muy
simple. Bueno, no es la forma ideal, pero es suficiente para explicar
conceptos. La función primero obtiene todas las claves del
almacenamiento en una lista y comprueba si está vacía o no. Si está
vacía, por tanto ningún elemento esta almacenado, devuelve `1` como
el próximo identificador. Y si la lista no está vacía, el próximo
identificador se calcula sumando `1` al valor máximo de la lista.
La función para registrar un huesped puede obtener el próximo
identificador usando la función `proximo_id`, y luego asignando
los detalles de un huesped usando un diccionario. Aquí está la función
para obtener los detalles y almacenar en la base de datos::
>>> def registrar_huesped(nombre, lugar):
... huesped_id = proximo_id()
... huespedes_db[huesped_id] = {
... 'nombre': nombre,
... 'lugar': lugar
... }
Aqui termina nuestro enfoque procedural. Será mucho más fácil
añadir funcionalidades necesarias como almacenamiento de datos,
diseño flexible y código testeable usando objetos.
Enfoque orientado a objetos
~~~~~~~~~~~~~~~~~~~~~~~~~~~
En una metodología orientada a objetos, puedes pensar en un objeto
registrador que se encargue del registro. Hay muchas ventajas para
crear un objeto que se encargue del registro. La más importante es
que la abstracción que ofrece el objeto registrador hace el código
más fácil de entender. Ofrece una forma de agrupar funcionalidad
relacionada, y puede ser ampliada con herencia. Cuando se añadan
mejoras, como cancelación y actualización de reservas, el objeto
registrador puede crecer para tenerlas, o puede delegarselas a
otro objeto::
>>> class RegistradorHuesped(object):
...
... def registrar(self, nombre, lugar):
... huesped_id = proximo_id()
... huespedes_db[huesped_id] = {
... 'nombre': nombre,
... 'lugar': lugar
... }
En esta implementación, el objeto registrador (una instancia de
la clase RegistradorHuesped) se encarga del registro. Con este
diseño, un objeto registrador en concreto puede realizar numerosos
registros.
Así es como puedes usar la implementación actual::
>>> registrador = RegistradorHuesped()
>>> registrador.registrar("Pepito", "Pérez")
Los cambios de requisitos son inevitables en un proyecto real. Considera
este caso, después de algún tiempo, un nuevo requisito se presenta:
los huespedes también deben dar el número de teléfono para que se les
admita. Necesitarás cambiar la implementación del objeto registrador
para ofrecer esto.
Puedes cumplir este requisito añadiendo un argumento al método
`registrar` y usar ese argumento en el diccionario de valores. Aquí
está la nueva implementación para este requisito::
>>> class RegistradorHuesped(object):
...
... def registrar(self, nombre, lugar, telefono):
... huesped_id = proximo_id()
... huespedes_db[huesped_id] = {
... 'nombre': nombre,
... 'lugar': lugar,
... 'telefono': telefono
... }
Además de migrar los datos al nuevo esquema, ahora tienes que cambiar
la forma de usar `RegistradorHuesped` en todos sitios. Si puedes
abstraer los detalles de un huesped en un objeto y usarlo en el
registro, los cambios en el código se pueden minimizar. Si sigues este
diseño, tienes que pasarle el objeto huesped a la función en lugar de
más argumentos. La nueva implementación con el objeto huesped quedaría
así::
>>> class RegistradorHuesped(object):
...
... def registrar(self, huesped):
... huesped_id = proximo_id()
... huespedes_db[huesped_id] = {
... 'nombre': huesped.nombre,
... 'lugar': huesped.lugar,
... 'telefono': huesped.telefono
... }
Bien, incluso con esta implementación tienes que cambiar código. El
cambio de código con nuevos requisitos es inevitable, tu objetivo es
poder minimizar los cambios y hacerlo mantenible.
.. note::
Debes tener el coraje de hacer cualquier cambio, grande o pequeño,
en cualquier momento. Retroalimentación inmediata es la única forma
de que tengas el coraje. El uso de los tests automáticos te dan la
retroalimentación inmediata y por tanto el coraje para hacer cambios.
Para más información sobre el tema, puedes leer el libro llamado
`Extreme Programming Explained` de Kent Beck.
Al introducir el objeto huesped, te has ahorrado un poco de escritura.
Más que eso, la abstracción del objeto invitado ha hecho tu sistema
mucho más simple y fácil de entender. Cuanto mejor se entienda mejor
se puede restructurar y por tanto mejor se mantiene el código.
El patrón adaptador
~~~~~~~~~~~~~~~~~~~
Como se ha dicho antes, en una aplicación real, el objeto registrador
puede tener funcionalidades de cancelación y/o actualización. Supón
que hay dos método más como, `cancelar_registro` y
`actualizar_registro`. En el nuevo diseño deberás pasar el objeto
huesped a ambos métodos. Puedes solucionar este problema guardando
el objeto huesped como un atributo del objeto registrador.
Aquí tenemos la nueva implementación del objeto registrador que
guarda el objeto huesped como un atributo::
>>> class RegistradorHuespedNG(object):
...
... def __init__(self, huesped):
... self.huesped = huesped
...
... def registrar(self):
... huesped= self.huesped
... huesped_id = proximo_id()
... huespedes_db[huesped_id] = {
... 'nombre': huesped.nombre,
... 'lugar': huesped.lugar,
... 'telefono': huesped.telefono
... }
La solución a la que has llegado es un patrón de diseño común llamado,
`Adaptador`. Con este diseño, ahora puedes añadir más métodos, es decir
más funcionalidad, si se necesita.
En esta implementación, al crear la instancia tienes que pasarle el
objeto invitado que tiene los valores como atributos. Ahora es
necesario crear instancias separadas de `RegistradorHuespedNG` para
cada objeto huesped.
Ahora retrocedamos y pensemos de otra forma. Supón que eres el creador
de este software y se lo vendes a muchos hoteles. Considera el caso en
el que tus clientes necesitan distintos almacenamientos. Por ejemplo,
un registrador puede almacenar los detalles en una base de datos
relacional y otro puede almacenarlos en la Base de datos orientada a
Objetos de Zope (ZODB). Sería mejor si puedes sustituir el objeto
registrador por otro que almacena los detalles de los huespedes de
otra forma distinta. Por tanto, un mecanismo para cambiar la
implementación basado en alguna configuración será útil.
La arquitectura de componentes Zope ofrece un mecanismo para sustituir
componentes basado en configuración. Utilizando la arquitectura de
componentes de Zope puedes registrar componentes en un registro llamado
registro de componentes. Después, puede recuperar componentes basandose
en la configuración.
La clase `RegistradorHuespedNG` sigue, como ya has visto, un patrón
llamado `Adaptador`. El `RegistradorHuespedNG` es el adaptador que
adapta el objeto huesped (adaptado). Como puedes ver, el adaptador
debe contener el objeto que adapta (adaptado). Esta es una
implementación típica de un adaptador::
>>> class Adaptador(object):
...
... def __init__(self, adaptado):
... self.adaptado = adaptado
Ahora el adaptador puede usar el adaptado (llamar a sus métodos o
acceder a sus atributos). Un adaptador puede adaptar más de un
componente. La arquitectura de componentes zope ofrece un mecanismo
para utilizar de forma efectiva este tipo de objetos. Así, qué
componente se use se convierte en un problema de configuración.
Este es un escenario común donde quieres usar objetos diferentes
para hacer las mismas cosas, pero los detalles varían. Hay muchas
situaciones en programación donde quieres usar diferentes
implementaciones para el mismo tipo de objetos. Te ofrecemos una
pequeña lista de otros escenarios comunes:
- Un motor wiki que soporte múltiples marcados (STX, reST, Texto
plano, etc.)
- Un objeto navegador que muestre el tamaño de distintos tipos
de objetos.
- Diferentes tipos de formatos de salida para datos de texto
(PDF, HTML etc.)
- Cuando se desarrolla una aplicación para múltiples clientes, sus
requisitos pueden cambiar. Mantener distintas versiones del código
de la misma aplicación para distintos clientes es difícil. Un
enfoque mejor sería crear distintos componentes reutilizables y
configurarlos basandose en los requisitos específicos de cada
cliente.
Todos estos ejemplos señalan situaciones donde quieres hacer
aplicaciones extensibles o enchufables. No utilices componentes
`adaptadores` cuando no quieras extensibilidad o enchufabilidad.
La arquitectura de componentes de Zope ofrece componentes `adaptadores`
para solucionar este tipo de problemas. De hecho,
`RegistradorHuespedNG` es un adaptador sin declaración de interfaz
explícita. Este tutorial tratará los adaptadores después de introducir
el concepto de interfaces. Las interfaces son una de las bases de los
componentes de Zope, por tanto entender el concepto y uso de interfaces
es muy importante.
Interfaces
----------
Introducción
~~~~~~~~~~~~
`Patrones de Diseño` es un libro clásico de ingeniería del software
escrito por la `Banda de los Cuatro` [#patternbook]_. En este libro
se recomienda: "Programa contra un interfaz, no contra una
implementación". Definir interfaces formales te ayuda a entender mejor
el sistema. Además, las interfaces traen consigo todos los beneficios
de la ZCA.
Las interfaces definen el comportamiento y el estado de objetos. Una
interfaz describe como se trabaja con el objeto. Si te gustan las
metaforas, piensa en la interfaz como un `contrato del objeto`. Otra
métoda que ayuda es `molde de objetos`. En el código, los métodos
y los atributos forman la interfaz del objeto.
La noción de interfaz es muy explícita en lenguajes modernos como
Java, C#, VB.NET etc. Estos lenguajes también ofrecen una sintaxis
para definir interfaces. Python tiene la noción de interfaces, per
no es muy explícita. Para simular una definición formal de interfaces
en C++, la `Banda de los Cuatro` utiliza clases con funciones
virtuales en el libro `Patrones de Diseño`. De forma similar, la
arquitectura de componentes de Zope utiliza la meta-clase heredada de
``zope.interface.Interface`` para definir una interfaz.
La base de la orientación a objetos es la comunicación entre los
objetos. Se utilizan mensajes para comunicación entre objetos. En
Python, funciones, métodos o cualquier otro llamable, puede usarse
para manipular mensajes.
Por ejemplo, considera esta clase::
>>> class Anfitrion(object):
...
... def buenosdias(self, nombre):
... """Le dice buenos dias a los huespedes"""
...
... return "¡Buenos días, %s!" % nombre
En la clase anterior, has definido un método `buenosdias`. Si llamas
al método `buenosdias` desde un objeto creado con esta clase, devolverá
`¡Buenos días, ...!`::
>>> anfitrion = Anfitrion()
>>> anfitrion.buenosdias('Pepe')
'¡Buenos días, Pepe!'
Aquí ``anfitrion`` es el objeto real. Los detalles de implementación de
este objeto es la clase ``Anfitrion``. Ahora, cómo se sabe cómo es el
objeto, es decir, cuáles son los métodos y los atributos del objeto.
Para responder a esto, tienes que ir a los detalles de implementación
(la clase ``Anfitrion``) del objeto o bien necesitas una documentación
externa de la API [#api]_.
Puedes usar el paquete ``zope.interface`` para definir la interfaz de
objetos. Para la clase anterior puedes especificar la interfaz así::
>>> from zope.interface import Interface
>>> class IAnfitrion(Interface):
...
... def buenosdias(huesped):
... """Le dice buenos dias al huesped"""
Como puedes ver, la interfaz se define usando la sentencia class de
Python. Usamos (¿abusamos de?) la sentencia class de Python para
definir interfaces. Para hacer que una clase sea una interfaz, debe
heredar de ``zope.interface.Interface``. El prefijo ``I`` de la
interfaz es una convención.
Declarando interfaces
~~~~~~~~~~~~~~~~~~~~~
Ya has visto como declarar una interfaz usando ``zope.interfaz`` en
la sección anterior. En esta sección se explicarán los conceptos en
detalle.
Considera esta interfaz de ejemplo::
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> class IAnfitrion(Interface):
... """Un objeto anfitrion"""
...
... nombre = Attribute("""Nombre del anfitrion""")
...
... def buenosdias(huesped):
... """Le dice buenos dias al huesped"""
La interfaz, ``IAnfitrion`` tiene dos atributos, ``nombre`` y
``buenosdias``. Recuerda que, al menos en Python, los métodos
también son atributos de clases. El atributo ``nombre`` se define
utilizando la clase ``zope.interface.Attribute``. Cuando añades
el atributo ``nombre`` a la interfaz ``IAnfitrion``, no especificas
ningún valor inicial. El propósito de definir el atributo ``nombre``
aquí es meramente para indicar que cualquier implementación de
esta interfaz tendrá una atributo llamado ``nombre``. En este
caso, ¡ni siquiera dices el tipo que el atributo tiene que tener!
Puedes pasar una cadena de documentación como primer argumento a
``Attribute``.
El otro atributo, ``buenosdias`` es un método definido usando
una definición de función. Nótese que no hace falta ``self``
en las interfaces, porque ``self`` es un detalle de implementación
de la clase. Por ejemplo, un módulo puede implementar esta
interfaz. Si un módulo implementa esta interfaz, habrá un atributo
``nombre`` y una función ``buenosdias`` definida. Y la función
``buenosdias`` aceptará un argumento.
Ahora verás como conectar `interfaz-clase-objeto`. Así objeto
es la cosa viva y coleante, objetos son instancias de clases. Y
la interfaz es la definición real del objeto, por tanto las
clases son sólo detalles de implementación. Es por esto por lo
que debes programar contra una interfaz y no contra una
implementación.
Ahora deberías familiarizarte con dos términos más para entender
otros conceptos. El primero es `proveer`y el otro es `implementar`-
Los objetos proveen interfaces y las clases implementan interfaces.
En otras palabras, objetos proveen las interfaces que sus clases
implementan. En el ejemplo anterior ``anfitrion`` (objeto) provee
``IAnfitrion`` (interfaz) y ``Anfitrion`` (clase) implementa
``IAnfitrion`` (interfaz). Un objeto puede proveer más de una
interfaz y tambíen una clase puede implementar más de una interfaz.
Los objetos también pueden proveer intefaces directamente, además
de lo que sus clases implementen.
.. note::
Las clases son los detalles de implementación de los objetos.
En Python, las clases son objetos llamables, asi que por qué
otros objetos llamables no pueden implementar una intefaz?
Sí, es posible. Para cualquier `objeto llamable` puedes declarar
que produce objetos que proveen algunas interfaces diciendo que
el `objeto llamable` implementa las interfaces. Generalmente
los `objetos llamables` son llamados `fábricas`. Como las
funciones son objetos llamables, una función puede ser la
`implementadora` de una intefaz.
Implementando interfaces
~~~~~~~~~~~~~~~~~~~~~~~~
Para declarar que una clase implementa una intefaz en particular,
utiliza la función ``zope.interface.implements`` dentro de la
sentencia class.
Considera este ejemplo, aquí ``Anfitrion`` implementa ``IAnfitrion``::
>>> from zope.interface import implements
>>> class Anfitrion(object):
...
... implements(IAnfitrion)
...
... nombre = u''
...
... def buenosdias(self, huesped):
... """Le dice buenos dias al huesped"""
...
... return "¡Buenos dias, %s!" % huesped
.. note::
Si te preguntas como funciona la función ``implements``, consulta
el mensaje del blog de James Henstridge
(http://blogs.gnome.org/jamesh/2005/09/08/python-class-advisors/) .
En la sección del adaptador, verás una función ``adapts``, que
funciona de forma similar.
Como ``Anfitrion`` implementa ``IAnfitrion``, instancias de
``Anfitrion`` proveen ``IAnfitrion``. Hay unos cuantos métodos
de utilidad que introspeccionan las declaraciones. La declaración
se puede escribir fuera de la clase también. Si no escribes
``interface.implements(IAnfitrion)`` en el ejemplo anterior,
entonces después de la sentencia class, puedes escribir algo como::
>>> from zope.interface import classImplements
>>> classImplements(Anfitrion, IAnfitrion)
Ejemplo revisado
~~~~~~~~~~~~~~~~
Ahora volvemos a la aplicación de ejemplo. Ahora veremos como
definir la interfaz del objeto registrador::
>>> from zope.interface import Interface
>>> class IRegistrador(Interface):
... """Un registrador registrará los detalles de un objeto"""
...
... def registrar():
... """Registrar detalles de un objeto"""
...
Aquí primero has importado la clase ``Interface`` del módulo
``zope.interface``. Si defines una subclase de esta clase ``Interface``,
será una interface desde el punto de vista de la arquitectura de
componentes de Zope. Una interfaz puede ser implementada, como ya
has visto, en una clase o cualquier otro objeto llamable.
La interfaz registrador definida aquí es ``IRegistrador``. La cadena
de documentación del interfaz da una idea del objeto. Al definir un
método en la interfaz, has creado un contrato para el componente, en
el que dice que habrá un método con el mismo nombre disponible. En
la definición del método en la interfaz, el primer argumento no debe
ser `self`, porque una intefaz nunca será instanciada ni sus métodos
serán llamados jamás. En vez de eso, la sentencia class de la interfaz
meramente documenta qué métodos y atributos deben aparecer en
cualquier clase normal que diga que la implementa, y el parámetro
`self` es un detalle de implementación que no necesita ser
documentado.
Como sabes, una interfaz puede también especificar atributos
normales::
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> class IHuesped(Interface):
...
... nombre = Attribute("Nombre del huesped")
... lugar = Attribute("Lugar del huesped")
En esta interfaz, el objeto huesped tiene dos atributos que se
especifican con documentación. Una interfaz también puede especificar
atributos y métodos juntos. Una interfaz puede ser implementada por
una clase, un módulo o cualquier otro objeto. Por ejemplo una
función puede crear dinámicamente el componente y devolverlo, en
este caso la función es una implementadora de la interfaz.
Ahora ya sabes lo que es una interfaz y como definirla y usarla. En
el próximo capítulo podrás ver como se usa una interfaz para definir
un componente adaptador.
Interfaces de marcado
~~~~~~~~~~~~~~~~~~~~~
Una interfaz se puede usar para declarar que un objeto en particular
pertenece a un tipo especial. Un interfaz sin ningún atributo o método
se llama `interfaz de marcado`.
Aquí tenemos una `interfaz de marcado`::
>>> from zope.interface import Interface
>>> class IHuespedEspecial(Interface):
... """Un huesped especial"""
Esta interfaz se puede usar para declarar que un objeto es un huesped
especial.
Invariantes
~~~~~~~~~~~
A veces te piden usar alguna regla para tu componente que implica
a uno o más atributos normales. A este tipo de reglas se les llama
`invariantes`. Puedes usar ``zope.interface.invariant`` para
establecer `invariantes` para tus objetos en sus interfaces.
Considera un ejemplo sencillo, hay un objeto `persona`. Una persona
tiene los atributos `nombre`, `email` y `telefono`. ¿Cómo implementas
una regla de validación que diga que o bien el email o bien el
teléfono tienen que existir, pero no necesariamente los dos?
Lo primero es hacer un objeto llamable, bien una simple función o
bien una instancia llamable de una clase como esto::
>>> def invariante_contactos(obj):
...
... if not (obj.email or obj.telefono):
... raise Exception(
... "Al menos una información de contacto es obligatoria")
Ahora defines la interfaz del objeto `persona` de esta manera.
Utiliza la función ``zope.interface.invariant`` para establecer la
invariante::
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import invariant
>>> class IPersona(Interface):
...
... nombre = Attribute("Nombre")
... email = Attribute("Direccion de email")
... telefono = Attribute("Numero de telefono")
...
... invariant(invariante_contactos)
Ahora usas el método `validateInvariants` de la interfaz para
validar::
>>> from zope.interface import implements
>>> class Persona(object):
... implements(IPersona)
...
... nombre = None
... email = None
... telefono = None
>>> pepe = Persona()
>>> pepe.email = u"pepe@algun.sitio.com"
>>> IPersona.validateInvariants(pepe)
>>> maria = Persona()
>>> IPersona.validateInvariants(maria)
Traceback (most recent call last):
...
Exception: Al menos una información de contacto es obligatoria
Como puedes ver el objeto `pepe` validó sin lanzar ninguna
excepción. Pero el objeto `maria` no validó la restricción de
la invariante, por lo que se lanzó la excepción.
.. [#patternbook] http://en.wikipedia.org/wiki/Design_Patterns
.. [#api] http://en.wikipedia.org/wiki/Application_programming_interface
Adaptadores
-----------
Implementación
~~~~~~~~~~~~~~
Esta sección describirá los adaptadores en detalle. La arquitectura
de componentes de Zope, como has notado, ayuda a usar objetos
Python de forma efectiva. Los adaptadores son uno de los componentes
básicos usados por la arquitectura de componentes de Zope para usar
los objetos Python de forma efectiva. Los adaptadores son objetos
Python, pero con una interfaz bien definida.
Para decir que una clase es un adaptador utiliza la función
`adapts' definida en el paquete ``zope.component``. Aquí tenemos
un nuevo adaptador `RegistradorHuespedNG` con una declaración de
interfaz explícita::
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class RegistradorHuespedNG(object):
...
... implements(IRegistrador)
... adapts(IHuesped)
...
... def __init__(self, huesped):
... self.huesped = huesped
...
... def registar(self):
... huesped = self.huesped
... huesped_id = proximo_id()
... huespedes_db[huesped_id] = {
... 'nombre': huesped.nombre,
... 'lugar': huesped.lugar,
... 'telefono': huesped.telefono
... }
Lo que has definido aquí es un `adaptador` para `IRegistrador`, que
adapta el objeto `IHuesped`. La interfaz `IRegistrador` es
implementada por la clase `RegistradorHuespedNG`. Así, una instancia
de esta clase proveerá la interfaz `IRegistrador`.
::
>>> class Huesped(object):
...
... implements(IHuesped)
...
... def __init__(self, nombre, lugar, telefono):
... self.nombre = nombre
... self.lugar = lugar
... self.telefono = telefono
>>> pepe = Huesped("Pepito", "Perez")
>>> registrador_pepe = RegistradorHuespedNG(pepe)
>>> IRegistrador.providedBy(registrador_pepe)
True
'RegistradorHuespedNG` es simplemente un adaptador que has creado,
puedes crear otros adaptadores que se encarguen del registro de
huespedes de forma diferente.
Registro
~~~~~~~~
Para usar este componente adaptador, tienes que registrarlo en
un registro de componentes también conocido como administrador
de sitios (site manager). Un administrador de sitios normalmente
reside en un sitio. Un sitio y un administrador de sitios
cobrarán más importancia al desarrollar una aplicación Zope 3.
Por ahora sólo necesitas molestarte con el sitio global y el
administrador de sitios global (o registro de componentes).
Un administrador de sitios globales permanecerá en memoria,
mientras que un administrador de sitios locales es persistente.
Para registrar tu componente, primero obtienes el administrador
de sitios global::
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(RegistradorHuespedNG,
... (IHuesped,), IRegistrador, 'ng')
Para obtener el administrador de sitios global, tienes que llamar
a la función ``getGlobalSiteManager`` disponible en el paquete
``zope.component``. En realidad, el administrador de sitios global
está disponible como un atributo (``globalSiteManager``) del
paquete ``zope.component``. Asi que puedes usar directamente el
atributo ``zope.component.globalSiteManager``. Para registrar
el adaptador, como ves más arriba, utiliza el método
``registerAdapter`` del registro de componentes. El primer
argumento debe ser la clase/fábrica de tu adaptador. El segundo
argumento es una tupla de objetos `adaptados`, i.e. el objeto
que estás adaptando. En este ejemplo, estás adaptando sólo el
objeto `IHuesped`. El tercer argumento es la interfaz que provista
por el componente adaptador. El cuarto argumento es opcional, es
el nombre de este adaptador en particular. Al darle un nombre a
este adaptador, es un `adaptador con nombre`. Si no se le da un
nombre, de forma predeterminada será la cadena vacía ('').
En el registro anterior, le has dicho la interfaz adaptada y la
interfaz a proveer por el adaptador. Como ya le habías dicho estos
detalles en la implementación del adaptador, no es necesario que
se especifique de nuevo. De hecho, podrías haber hecho el registro
así::
>>> gsm.registerAdapter(RegistradorHuespedNG, name='ng')
Hay unas API antiguas para hacer el registro, que debes evitar. Las
funciones antiguas empiezan por `provide`, por ejemplo,
``provideAdapter``, ``provideUtility``, etc. Cuando desarrollas
una aplicación Zope 3 puedes usar el lenguaje de marcas de
configuración de Zope (ZCML) para registrar componentes. En Zope 3,
los componentes locales (componente persistentes) pueden registrarse
desde la Interfaz de Administración de Zope (ZMI) o también pueden
registrarse programáticamente.
Has registrado `RegistradorHuespedNG` con el nombre `ng`. De forma
similar puedes registrar otros adaptadores con nombres distintos. Si
un componente se registra sin nombre, de forma predeterminada será
la cadena vacía.
.. note::
Los componentes locales son componentes persistentes pero los
componentes globales están en memoria. Los componentes globales
serán registrados basandose en la configuración de la aplicación.
Los componentes locales se llevan a memoria desde la base de datos
en el inicio de la aplicación.
Patrón de consulta
~~~~~~~~~~~~~~~~~~
Recuperar componentes registrados a partir del registro de componentes
se consigue mediante dos funciones disponibles en el paquete
``zope.component``. Una de estas es ``getAdapter`` y la otra es
``queryAdapter``. Las dos funciones aceptan los mismos argumentos.
``getAdapter`` lanzará ``ComponentLookupError`` si la búsqueda del
componente falla mientras que ``queryAdapter`` devolverá `None`-
Puedes importar estos métodos así::
>>> from zope.component import getAdapter
>>> from zope.component import queryAdapter