Está en la página 1de 15

Capitulo 3

Jerarquía de Collection
La jerarquía de Collection es una de las más importantes dentro de un
ambiente vivo de objetos pensado para generar modelos computacionales.
Esto se debe a que en la gran mayoría de los dominios existen entes de la
realidad que son o contienen colecciones de otros entes.

Dado esto, es muy importante contar en el ambiente con colecciones de


diferentes características: con o sin orden, con elementos únicos o repetidos,
de tamaño fijo o variable. A su vez el modelo que implemente estas
colecciones debe ser flexible, extendible y performeante. Esta última
característica es muy importante, ya que afectará la performance de otros
modelos creados dentro del ambiente.

En capítulos anteriores se mencionó que la nueva jerarquía implementada


estaría acotada por las clases definidas por el ANSI, así como también, por los
protocolos especificados para cada una de estas. Esta decisión se tomo porque
se debía acotar el dominio de las colecciones, ya que el mismo es muy amplio
y puede haber diferentes interpretaciones de cuáles son las colecciones a
implementar y sobre cuál es su comportamiento.

Este capítulo describe, en primer lugar, la jerarquía de protocolos definida por


el ANSI para las clases concretas que el mismo documento especifica. A partir
de estas definiciones queda acotado el dominio modelado en este trabajo.

Una vez definido el dominio, se realiza una descripción de la jerarquía


implementada en Squeak. Además se analizan los principales problemas que
presenta esta implementación de la jerarquía de Collection.

Con el fin de dar un primer acercamiento al nuevo modelo generado, al


finalizar este capítulo se presenta la nueva jerarquía de clases implementada
para modelar las colecciones especificadas por el ANSI.

Jerarquía de Collection en el ANSI


Según se describe en el documento ANSI de Smalltalk, el standard debía
cumplir tres objetivos principales:

• basándose sólo en el ANSI se debía poder implementar un Smalltalk,

• los programas Smalltalk que cumplan con la especificación debían tener


la misma semántica en cualquier implementación del lenguaje generada,

• la especificación debía ser lo suficientemente completa para permitir


crear programas Smalltalk útiles.
Si bien no existe descripción alguna sobre interfaces de usuario o sobre algún
framework de base de datos, el documento es bastante consistente respecto a
sus objetivos.

Un caso en donde se ve demostrado es en la jerarquía de Collection. Para esta


jerarquía, el documento dedica una extensa sección donde define las
colecciones concretas que todo Smalltalk debe tener implementado.

Para especificar el comportamiento de cada una de las colecciones concretas,


la especificación define una jerarquía de protocolos abstractos. Estos
protocolos están agrupados por las características esenciales de cada una de
las colecciones. En la Figura 3.1 se puede ver la jerarquía completa que
define el ANSI para las colecciones. Cabe aclarar que no todas ellas entran en
el alcance de este trabajo, esto se explicará en detalle en la última sección de
este capítulo.

Figura 3.1: Protocolos de la jerarquía de Collection definidos por el ANSI.

El primer detalle que se debe tener en cuenta es que los protocolos de clases
concretas con las que el ambiente debe contar comienzan con mayúsculas. El
ANSI no exige que se implementen en clases el resto de los protocolos
abstractos que define. No obstante, exige que las clases concretas
implementen los protocolos con los que están relacionadas en el gráfico.

El primer protocolo que merece ser descripto es el de <collection>, ya que el


mismo es común a todas las colecciones. En este protocolo se describen los
mensajes para manipular y operar sobre una colección de objetos, ya sea
individualmente o como un todo. A grandes rasgos se define comportamiento
para operar sobre los elementos, convertir la colección a otro de los tipos
definidos y para verificar su vacuidad.

Comenzando de izquierda a derecha, el siguiente protocolo abstracto es


<abstractDictionary>. Este provee los mensajes para acceder, agregar,
remover, e iterar sobre los elementos de una colección sin orden, cuyos
elementos son accedidos utilizando una clave que se les asigna explícitamente.
Hay dos protocolos de clases concretas que deben implementar
<abstractDictionary>, estos son: <Dictionary> e <IdentityDictionary>. El
primero representa a las colecciones con las características de
<abstractDictionary> donde la equivalencia de su clave se define enviando el
mensaje #=. La diferencia con el segundo protocolo radica en que la
equivalencia es definida enviando el mensaje #==1. Ninguno de estos
protocolos agrega comportamiento a estas colecciones.

El siguiente protocolo abstracto es <extensibleCollection>, este define


comportamiento básico para agregar y eliminar elementos de una colección de
tamaño variable.

Los protocolos para las clases concretas <Set> y <Bag> deben implementar el
protocolo <extensibleCollection>. El primero de estos representa una colección
de tamaño variable, sin orden entre sus elementos. Además, sus elementos no
pueden ser accedidos individualmente a través de una clave externa, y de la
misma manera que los conjuntos matemáticos, no pueden tener elementos
repetidos. <Bag> es similar a <Set>, pero al contrario que este último permite
elementos repetidos.

Continuando de izquierda a derecha se encuentra


<sequencedContractibleCollection>, este protocolo define comportamiento
para remover elementos de una colección ordenada de objetos, cuyos
elementos puede ser accedidos usando una clave externa representada por un
entero o índice2.

El siguiente protocolo abstracto es <sequencedReadableCollection>, el cual


define comportamiento para leer colecciones ordenadas cuyos elementos
pueden ser accedidos individualmente utilizando un índice. El mismo debe
estar entre uno (1) y el número de elementos de la colección inclusive.

Utilizando el comportamiento definido en <collection>,


<extensibleCollection>, <sequencedContractibleCollection> y
<sequencedReadableCollection> se define el protocolo para la clase concreta
<SortedCollection>. Esta clase representa una colección de objetos de tamaño
variable, cuyos elementos están ordenados basados en un criterio de
ordenamiento. Este criterio se define en un bloque que recibe el nombre
sortBlock. Los elementos pueden ser agregados y removidos, y además, estos
pueden ser accedidos individualmente mediante un índice.

1
El mensaje #== retorna un objeto del tipo Boolean indicando si ambos objetos son
exactamente la misma instancia.
2
Un índice es una clave de búsqueda a través de la cual se puede acceder al elemento
indicado por el valor del mismo.
Otro de los protocolos abstractos definidos por el ANSI es
<sequencedCollection>, este provee comportamiento para escribir en una
colección ordenada de objetos, cuyos elementos pueden ser accedidos
mediante un índice.

La clase concreta <OrderedCollection> debe implementar el protocolo definido


para sí misma y además debe responder a los mensajes definidos en los
protocolos: <collection>, <extensibleCollection>,
<sequencedContractibleCollection>, <sequencedReadableCollection> y
<sequencedCollection>. Esta representa una colección ordenada de objetos,
de tamaño variable. Al igual que en SortedCollection se pueden agregar,
eliminar o insertar elementos. A su vez, estos pueden ser accedidos a través de
un índice. La diferencia entre ambas colecciones radica en que el orden en
<OrderedCollection> está dado por el orden en que se agregan los elementos,
estos siempre se agregan al final de la colección.

Otra de las clases concretas definidas por el ANSI es <Interval>. Además de


implementar protocolo propio de un intervalo, esta clase debe proveer el
comportamiento definido en <sequencedReadableCollection> y en
<collection> (como todas las demás colecciones). Los intervalos representan
colecciones cuyos elementos son números que forman una progresión
aritmética.

Algunas de las clases concretas definidas por el ANSI bajo la jerarquía de


Collection quedaron fuera del alcance del proyecto. Estas son: <Array>,
<ByteArray>, <String> y <Symbol>. A continuación se describe el porqué de
esta decisión.

Tanto Array como ByteArray quedaron fuera del alcance por cuestiones
implementativas, ya que son clases bien conocidas por la Virtual Machine (VM).
Es decir, que la VM conoce su estructura interna y por lo tanto utiliza esta
información para realizar optimizaciones. Además, estas clases deben
subclasificar de su superclase utilizando mensajes especiales y no todo su
comportamiento es implementado a través de colaboraciones entre objetos.

En Squeak la clase Array subclasifica ArrayedCollection con un mensaje


especial:

variableSubclass:instanceVariableNames:classVariableNames:poolDictionarie
s:category:

A diferencia del mensaje común utilizado para subclasificar, este es el mensaje


estándar de inicialización para crear una nueva clase como una subclase de
una clase existente (el receiver) en donde la subclase tiene variables de
punteros indexables.
Al mismo tiempo, muchos de los métodos implementados en Array utilizan, por
cuestiones de performance, primitivas en lugar de colaboraciones entre
objetos. Por lo tanto, si se implementa esta clase sin utilizar primitivas la
performance se degradará notablemente. En caso de decidir implementarla
haciendo uso de las primitivas, prácticamente se deberían trasladar las
implementaciones de la clase actual a la nueva.

Con la clase ByteArray ocurre algo similar, la diferencia radica en que el


método utilizado para subclasificar ArrayedCollection es:

variableByteSubclass:instanceVariableNames:classVariableNames:poolDictio
naries:category:

Al igual que el mensaje anterior, la subclase tiene variables de punteros


indexables, pero estas variables tienen el tamaño de un byte.

Tal como sucede con Array, algunos de sus métodos se implementan utilizando
primitivas y se consideró innecesario implementar esta colección para cumplir
con el objetivo del trabajo.

Respecto a las otras dos clases concretas no implementadas: String y Symbol,


estas quedaron fuera del alcance, en parte, por el mismo motivo anteriormente
expuesto para las clases Array y ByteArray. La VM conoce la implementación
de las mismas y hace uso de su estructura interna por lo tanto no es posible
modificarlas. Además, su protocolo escapa al propio de las colecciones, ya que
agrega protocolo definido en <Magnitude>.

El ANSI también define el protocolo para la creación de instancias de cada


colección. Por ejemplo, el protocolo <collection factory> define los mensajes
necesarios para crear una colección de objetos. Estos protocolos son mensajes
que las clases del modelo a implementar deben saber responder.

En la Figura 3.2 se pueden visualizar todos los protocolos de creación de


instancias que define el ANSI y las relaciones entre ellos.

Figura 3.2: Protocolos de creación de instancias de la jerarquía de Collection definidos por el ANSI.
Jerarquía de Collection en Squeak
En Squeak, como en toda implementación de Smalltalk, se encuentra
desarrollado un modelo de la jerarquía de Collection. En esta sección se
describe este modelo y además se realiza un análisis de los principales
problemas del mismo.

Descripción
En Squeak, la jerarquía de Collection cuenta con la implementación de las once
colecciones concretas descriptas por el ANSI, sin embargo ésta jerarquía
implementa una gran cantidad de comportamiento que no está relacionado
con lo que se define en el ANSI. En concreto, la clase abstracta Collection
cuenta con 98 subclases, donde muchas de estas (por ejemplo,
CompiledMethod y Bitmap) son de propósito específico.

A continuación se realizará una descripción de la implementación en Squeak


del protocolo definido por el ANSI, las clases que componen a esta jerarquía se
pueden ver en la Figura 3.3.
Figura 3.3: Jerarquía de Collection en Squeak restringida al ANSI.

En Squeak la jerarquía comienza subclasificando a Object con la clase


abstracta Collection. En ella se implementa gran parte del protocolo común a
todas las colecciones definido por el ANSI. Sin embargo, gran parte de este
protocolo es re-implementado por las subclases, en algunos casos para realizar
optimizaciones y en otros porque las implementaciones que tiene Collection no
respetan el comportamiento especial que requiere la clase que hereda este
comportamiento.

Existen otras dos colecciones abstractas en el modelo: ArrayedCollection y


SequenceableCollection. La primera de ellas es subclase de la segunda, donde
la segunda es subclase de Collection y representa, según la documentación en
Squeak, una superclase abstracta para colecciones que tengan un orden bien
definido entre sus elementos. De esta manera, cada elemento puede ser
referenciado externamente por un entero como si fuese un índice.
Además de la clase ArrayedCollection, en la cual se pondrá atención luego, de
SequenceableCollection subclasifican dos de las clases concretas definidas por
el ANSI: Interval y OrderedCollection. Esto parece tener sentido, ya que en
ambas colecciones los elementos respetan un orden y pueden ser accedidos
externamente a través de índices. Sin embargo, algo que llama la atención es
que de OrderedCollection subclasifica SortedCollection. En la siguiente sección
se hará especial hincapié en analizar este tipo de decisiones.

De la clase abstracta ArrayedCollection subclasifican otras tres clases


concretas especificadas en el ANSI: Array, ByteArray y String. De esta última
subclasifica Symbol.

Tanto Set como Bag subclasifican Collection implementando el


comportamiento descripto por el ANSI. Por más que no parezca intuitivo, de
Set subclasifica Dictionary. En la siguiente sección se analizará esta relación en
detalle.

Por último queda IdentityDictionary, su superclase es Dictionary, que es quien


provee todo el comportamiento definido para el protocolo abstracto
<abstractDictionary>.

Análisis de la jerarquía de Squeak


En esta sección se realiza un análisis de la jerarquía de Collection de Squeak,
describiendo los problemas más significativos encontrados. El análisis se basa
fundamentalmente en las clases que forman parte del protocolo definido por el
ANSI.

Como William Cook [6] mostró, en la jerarquía de Collection se intenta


maximizar el reuso de código dejando de lado la categorización conceptual
entre las clases. Por ejemplo, la clase Dictionary es subclase de Set. La
decisión de relacionar estas clases a través de la subclasificación, está basada
en que comparten su implementación sin considerar la diferencia conceptual
entre lo que reifica cada clase. Este problema es denotado con el nombre: mal
uso de la subclasificación.

Otra forma de maximizar el reuso de código es implementando mensajes en un


nivel superior al que corresponde dentro de la jerarquía, buscando así que la
mayor cantidad posible de subclases puedan responder a estos mensajes. A
este problema se lo llamó métodos definidos muy arriba.

En la jerarquía de Collection de Squeak, otra problemática producto de la


subclasificación mal utilizada, es la gran explosión de clases con la que cuenta,
que se la titula como explosión de clases.
En lo que resta de esta sección se analiza con mayor profundidad cada uno de
estos problemas de diseño.

Mal uso de la subclasificación


Dentro de la jerarquía de Squeak, se puede encontrar el mal uso de la
subclasificación en varios ejemplos. En la Figura 3.4 vemos cómo la clase
Dictionary es subclase de Set, cuando en la realidad no tienen relación
conceptual. En cambio, su relación esta dada por como en Squeak se
implementa Dictionary. La implementación de Dictionary utiliza un conjunto de
asociaciones (instancias de la clase Association), las cuales relacionan a las
claves con un valor.

FIGURA 3.4: Subclasificación mal utilizada entre Dictionary y Set.

Uno de los problemas que se pueden producir al realizar este tipo de


subclasificación, es el de agregar en las subclases protocolo de las superclases
y que éste no sea consistente con el concepto que se esta creando.

Un ejemplo concreto de este problema se puede visualizar en el mensaje


#remove:, éste es heredado de la clase Collection. Claramente el mismo no
forma parte del protocolo de los diccionarios, dado que el colaborador
esperado en este mensaje es un elemento de la colección. En los diccionarios
no es posible acceder a los elementos a menos que sea mediante una clave,
por esto existe dentro de su protocolo el mensaje #removeKey:. Para
solucionar este problema en la jerarquía de Squeak se cancela el mensaje
#remove: implementándolo enviando el mensaje #shouldNotImplement, el
cual es utilizado para declarar que el mensaje no debe ser definido en la clase.

Por otro lado, sucede algo similar con el mensaje #add:, pero con una solución
diferente. La clase Dictionary puede responder al mensaje #add:, que también
es heredado de su superclase, o sea Set. Así como con el mensaje #remove:,
esto no tiene mucho sentido, ya que un diccionario se debe acceder mediante
una clave (mensaje #at:) y eventualmente para esa clave, modificar o definir
su valor (mensaje #at:put:).
Pero en este caso, el mensaje no es cancelado como #remove:, sino que recibe
como colaborador una instancia de la clase Association. Por lo tanto, que el
mensaje #add: definido en la clase Dictionary no es intuitivo para el
programador. Además al recibir como colaborador a una instancia de la clase
Association, está exponiendo hacia el exterior de qué manera se guardan los
elementos.

El mal uso de la subclasificación en la jerarquía de Squeak también se puede


observar en la clase SortedCollection, la cual es subclase de OrderedCollection.
Más allá de entrar en discusiones sobre la relación conceptual entre
SortedCollection y OrderedCollection, en este caso, el problema es de otra
índole. En esta ocasión se está subclasificando a una clase concreta, lo cual es
un error conceptual grave.

Como se mencionó anteriormente, esto se debe a que en la jerarquía de


Squeak, se priorizó el reuso de código ante la utilización de la subclasificación
como una herramienta para organizar conceptos.

Métodos definidos muy arriba

La subclasificación es muy utilizada dentro de la jerarquía de Collection, pero el


uso excesivo de la misma ha llevado a la creación de una jerarquía más
compleja de lo necesario.

Existen gran cantidad de métodos definidos muy arriba en la jerarquía que


luego son sobrescritos en las subclases.

Inicialmente esto no parecería ser un inconveniente, ya que la posibilidad de


definir comportamiento común en superclases para luego ser reutilizado desde
sus subclases, es uno de los grandes beneficios que provee la subclasificación
simple. La problemática se da porque la gran mayoría de veces este
comportamiento definido en las superclases, no es expandido o utilizado por
las subclases, sino que es sobrescrito y por lo tanto cancelado por las
subclases.

Como consecuencia de este problema, la tarea de comprender el


comportamiento definido en una clase que hereda varios métodos sin
utilizarlos, pasa a ser más compleja de lo necesario.

Esto es así porque se supone que la subclasificación brinda al programador una


herramienta intuitiva para comprender un modelo. Para hacerlo, en general se
comienza el proceso de aprendizaje desde la raíz de la jerarquía, y luego se
recorren las subclases analizando diferencias de comportamiento entre las
clases y sus superclases.

Dado que existen métodos implementados en un nivel superior al


correspondiente, el aprendizaje de la jerarquía se hace más complejo, ya que
el programador no puede analizar las diferencias entre clases y superclases
sino que tiene que considerar ambas en el proceso.

De esta manera, se logra que la subclasificación pase de ser un mecanismo


para ayudar al programador, a uno que haga más compleja la tarea de
comprender el modelo.

Un ejemplo de esta problemática se puede ver en la Figura 3.5. En este caso,


el mensaje definido en un nivel superior al correspondiente es #collect:, el cual
esta definido en la clase Collection.

FIGURA 3.5: Redefinición del mensaje #collect: en las superclases


SortedCollection.

Dentro de la implementación del mensaje #collect: en la clase Collection, se


envía el mensaje #add:, al cual no todas las subclases de Collection saben
responder (por ejemplo, la clase Interval). Este problema genera que muchas
subclases tengan que re-implementar este mensaje, por ejemplo la clase
abstracta SequenceableCollection lo hace, utilizando el mensaje #at:put: en
vez del #add: en su implementación.

Sin embargo OrderedCollection, que subclasifica SequenceableCollection,


vuelve a implementar el mensaje #collect: pero en este caso utilizando el
mensaje #addLast:. Por último, la clase SortedCollection que subclasifica
OrderedCollection vuelve a implementar el mensaje #collect:. En este caso, es
necesario modificar la clase resultante luego de recolectar los resultados,
puesto que no se puede asegurar que al aplicar la transformación definida en
el bloque enviado como colaborador en el mensaje, los resultados sean
comparables entre si para permanecer ordenados.

Explosión de Clases
Otro problema que ocurre en la jerarquía de Squeak (se consideran aquí, a
todas las clases de la jerarquía, no solamente a las definidas en el ANSI) es la
gran explosión de clases que existe. Como ya se mencionó en este capítulo, la
misma cuenta con 98 subclases. Entre otros motivos, este inconveniente es
una consecuencia del problema ya tratado de subclasificaciones mal utilizada.

El contar con una jerarquía de varios niveles, agrega mucha complejidad al


programador a la hora de modificar la jerarquía y también para comprenderla.
Por ejemplo cuando se crea una nueva clase, se hace muy complejo
determinar cual es su superclase.

Se pueden citar varios ejemplos de clases que no corresponden dentro de la


jerarquía de Squeak, como por ejemplo, CompiledMethod que representa a un
método compilado, claramente este concepto no está relacionado con el
dominio de las colecciones mas allá de utilizar a las colecciones como una
posible implementación.

Nueva Jerarquía de Collection


En esta sección se realiza un primer acercamiento a la jerarquía de clases
definitiva generada para modelar las colecciones definidas por el ANSI. Cabe
aclarar, que por ahora no se mencionarán los Traits que utiliza cada una de
estas clases.

La nueva jerarquía generada se verá en capítulos posteriores, donde se


transmite cómo evolucionó el modelo hasta llegar a su estado final y se
muestran los Traits implementados. Dejando de esta manera, una idea clara de
cómo está implementada la jerarquía.

En la Figura 3.6 se puede observar la jerarquía de clases, la cual muestra a


Collection como superclase de todas las demás. Si bien no se ve en el gráfico,
esta subclasifica a la clase Object.
Figura 3.6. Jerarquía de clases del nuevo modelo.

Como se puede ver en el gráfico Collection es una clase abstracta al igual que
las clases DictionaryBehavior y SetBehavior.

DictionaryBehavior es superclase de las clases Dictionary e IdentityDictionary,


en esta clase se agrupa el comportamiento común a los diccionarios y se
delega a sus subclases algunos mensajes con el fin de que implementen lo que
las diferencia, la equivalencia entre sus claves.

La otra clase abstracta es SetBehavior, de esta subclasifican las clases Set e


IdentitySet. Si bien esta última no es definida por el ANSI, fue desarrollada para
implementar comportamiento en IdentityDictionary.

Las demás clases concretas: Bag, OrderedCollection, SortedCollection e


Interval, todas subclasifican directamente de Collection. Esto marca una
notable diferencia respecto a la jerarquía implementada en Squeak por
diferentes motivos. En primer lugar, en este modelo ya no existe la clase
abstracta SequencedCollection de donde subclasificaban OrderedCollection e
Interval.

En segundo lugar, SortedCollection ya no es subclase de OrderedCollection.


Esto parece ser razonable, ya que según el ANSI, OrderedCollection debe
implementar cierto protocolo que no es propio de SortedCollection, y que esta
última hereda a través de la subclasificación.

Una vez presentada la especificación del dominio, la cual fue tomada de guía
durante el desarrollo, y luego de haber presentado la nueva jerarquía de
Collection, se comenzará a explicar el modelo generado. Para esto se
comenzará relatando la evolución del mismo, marcando las principales
modificaciones realizadas durante el desarrollo, para luego presentar el modelo
final generado. En este caso, se incluyen los Traits reificados y cómo estos son
utilizados desde las clases del modelo.
Referencias

[11] William R. Cook. Interfaces and specifications for the Smalltalk-80 collection
classes. In Proceedings OOPSLA ’92, ACM SIGPLAN Notices, volume 27, pages 1–15,
October 1992.

También podría gustarte