Administración del Conocimiento – UML Simulation

Traits: Composable Units of Behaviour

Posted in POO, Traits, UML by smalltalkuy on febrero 4, 2010

Traits: Unidades acoplables de comportamiento

Se han intentando varias formas de extender el comportamiento de los sistemas que usan herencia simple (la mayoría de los ambientes de programación orientada a objetos – POO), como ser, herencia múltiple, «mix in«, «traits«. Estos últimos (los traits) son los que más me convencieron, al agregar comportamiento de forma ortogonal a un sistema. La herencia múltiple y los mix in adolecen de varios problemas que los traits no.

Los traits son esencialmente son «métodos puros» (no pertenecen a ninguna clase en si) que implementan determinado comportamiento, y requieren determinadas entradas. Si un trait («account basic») tiene como requerimiento dos mensajes (#transactions #maxTransValue), cualquier clase del sistema que implemente (#transactions #maxTransValue) podrá utilizar este trait. En este caso el trait («account basic») también ofrece mensajes para consumir. Podrían ser: (#numberOfTransactions #transactionsBiggerThan: #transactionsFromCountry: ….), estos mensaje pueden ser invocados por cualquiera de las clases que implementan (#transactions #maxTransValue).

El código fuente que se ejecuta dentro del trait es el mismo para todas las clases, lo que difieren son los métodos (#transactions #maxTransValue) en cada clase, por lo que es una forma de re-utilización. La re-utilización es ortogonal ya que cada clase puede usar cualquier trait de forma independiente.

Los traits al ejecutarse difieren un poco de los métodos normales, como los traits no tienen herencia la palabra «super» se usa para denotar la clase que esta usando el trait en ese momento (ejemplo: super calculateAverage – se esta llamando a la implementación #calculateAvarage de la clase que esta usando el trait en ese momento).  «self» indica al objeto receptor del mensaje cuando se pasa como parámetro y se refiere al trait cuando se hace el lookup del mensaje (es decir «self foo» primero se busca #foo en el trait).

Método Normal

FixedAccount>>totalRates

«en este caso -super normalRates– llama a la implementacion <normalRates> de la super clase de  FixedAccount«

^self fixedRates + super normalRates

Método de un Trait

Trait>>transactionBiggerThan: aRate «super dentro de un trait va a la implementación de la clase que lo esta usando en el momento.

Va a la implementación #transactions de FixedAccounts, cuanto lo ejecuta otra clase –> va a #transactions de esa otra clase«

^super transactions select: [:each | each averageRate > aRate]

Esta es la única variante en la ejecución de los traits con los métodos normales.

Los traits se pueden usar en cualquier dominio, y no agregan complejidad al sistema como es el caso de la herencia múltiple y los «mix in«.

Modificando la máquina virtual para soportar Traits

Al modificar la máquina virtual se re implementa el dispatcher de mensajes, y cuando no se ha encontrado la implementación de un mensaje entonces se busca en los Traits de esa clase, si se encuentra se ejecuta sino se sigue el normal proceso. También hay que identificar en el momento de la ejecución que el super dentro del Trait indica la clase que esta usando el trait y no la super clase.

En esta figura se muestra de forma gráfica como es un Trait. En este caso la clase Circle tiene asociados dos traits: TCircle y TDrawing. Por lo que Circle responde a los mensajes que implementa por si misma, más los mensajes que ofrecen sus traits.

Circle implementa los mensajes (#initialize #drawOn: #center #center: #radius #radius:  )

TCircle implementa los mensajes (#= #hash … #area #bounds #scaledBy: …) y tiene como pre requisitos los mensajes (#center #center: #radius #radius:  )

TDrawing implementa los mensajes (#draw #refresh #refreshOn:  ) y tiene como pre requisitos los mensajes (#drawOn: #bounds).

Como la clase Circle cumple con los pre requisitos de TCircle y TDrawing puede hacer uso de estos dos traits. Como los traits puede ser usados en jerarquías de clases no relacionadas, forman diferentes bloques de comportamiento que puede ser usado de forma ortogonal en el sistema que los soporta. Los traits no soportan herencia (y esta bien que sea así) pero si soportan composición como se ve en la figura (TDrawing hace uso de TCircle).

Si un trait es modificado todas las clases que usan ese trait se veran afectas por el cambio. Los traits son una herramienta excelente para la re-utilizacion de comportamiento de forma ortogonal, cosa que no era posible cuando se usa herencia simple en un sistema. Si bien los traits se pueden implementar re escribiendo el método #doesNotUnderstand:, es mejor modificar la máquina virtual.

Los traits tienen varias ventajas sobre la herencia múltiple y los «mix in«:

Herencia múltiple

* Compleja, si no se utiliza con cuidado puede ser una arma de doble filo.

* Pueden ocurrir dos tipos de conflicto: de variables de instancia y de métodos. Cual de ellos usar cuando ocurre un conflicto ?. Esto se denomina el problema del diamante.

Mix in

* No voy a entrar en detalle de los mix in, pero es una especificación de una subclase que se puede aplicar a más de una super clase, de esta forma se extiende el comportamiento de más de una clase con el mismo mix in. La mayor desventaja de los mix in, y por eso no los usaria es que la jerarquía de clases que se genera es sumamente fragil.

Traits

Los traits tambien pueden tener conflictos a nivel de métodos, pero esto se soluciona facilmente puediendole poner alias o resolviendo el conflicto a nivel de la clase. Ya que cada clase puede cablearse con un trait de forma diferente, y esto no afecta al trait en si.

Trait Browser

La siguiente figura muestra un Trait Browser de la máquina virtual que estamos construyendo. Tiene una colección de packages (Collections, External Files, Values, VM Core) y cada package puede tener un conjunto de Traits. En este caso estamos viendo los traits correspondientes a las colecciones, por el momento solamente tenemos un trait llamado <UML Relations Methods>. Este trait es tremendamente útil, y se utiliza para todas las clases de cualquier modelo UML, por lo que su re-utilización es muy buena.

La idea de este trait es la siguiente, imaginen una clase UML con muchas relaciones. Por ejemplo: Expediente, un expediente tiene caratulas, partes, decisiones judiciales, expedientes relacionados, archivos, funcionariosdocumentos, etc (en relación NxN). Ahora si no tuvieramos los traits tendriamos que crear dos métodos por cada relación para agregar y remover objetos (y así para todas las clases del sistema, lo que llevaría mucho tiempo).

Los métodos serían #addCaratula: #removeCaratula: #addParte: #removeParte: #addDecisionJudicial: #removeDecisionJudicial: y así sucesivamante.  Pero gracias a este trait NO es necesario escribir código alguno, ya que asociamos el trait a la clase Expediente y tenemos los métodos #add: y #remove: al instante.

El trait obtiene la clase del objeto pasado como parámetro y determina que colección debe usar para realizar la operación. Este trait es utilizado por todas las clases del modelo UML, por lo que no hay necesidad de escribir código para relacionar los objetos. Notar que el código del trait dice <super xxxx>, este super indica la clase que hace uso del trait en ese momento (como se menciono anteriormente).

unExpediente add: unaParte.

unExpediente add: unaDecisionJudicial.

«es el método #add: (o #remove:) que se encarga de obtener la colección correspondiente dependiendo del parámetro»

Referencias

Traits: Composable Units of Behaviour

Nathanael Scharli, Stéphane Ducasse, Oscar Nierstrasz, and Andrew P. Black

Software Composition Group, University of Bern, SwitzerlandOGI School of Science & Engineering, Oregon Health and Science University