Rasgos Scala y decoradores
Fecha de publicación : 15/08/2010
Por Sylvain Leroux ( Inicio ) ( About Me)
En el primer enfoque, características Scala similares a las interfaces (como se define Java). Salvo que sólo una interfaz declarar métodos, mientras que una línea tiene la posibilidad de implementar y para declarar Miembros de datos.
Pero su uso es muy superior al de una interfaz mejorada. Así, en este artículo, vamos a ver cómo es posible combinar las características de una clase a desarrollar comportamientos específicos.
En este artículo se asume un conocimiento básico de la sintaxis de la Scala.
I. Introducción
II. En el paquete
III. Ofertas modulares
III -A. Herencia simple
III -B. Decorador
III -C. Características
III- C-1. estupendo y linealización
III- C -2. Mezclar varias características
III-D . Informe mixins
IV. ¿Qué rasgos ...
V. Conclusión
VI. Recursos
VII. Gracias
I. Introducción
Para este artículo , se procederá a partir de un caso muy simple, poco a poco iremos complicados para ver qué opciones están disponibles para implementar una solución en la Scala.
El ejemplo que servirá como ejemplo aquí es el de una suscripción de software de facturación telefónica . forma rudimentaria , cada cuenta es un objeto con un método para imputar una comunicación:
Compte.scala |
|
Podríamos asegurar el funcionamiento de una cuenta con el programa de pruebas siguientes :
Chapitre_I.scala |
|
Si lanza la demo podrás obtener el siguiente resultado:
|
Bien, nada muy emocionante en el momento . A lo sumo , podemos asegurar que las llamadas se están contando en el segundo. Pero sabes , no es como funciona todas las suscripciones de teléfono ...
II. En el paquete
Hasta ahora , hemos introducido un segundo de facturación . Pero muy a menudo el usuario opta por un paquete . Como el código de nuestra aplicación como un hecho Juan tiene un paquete de "2:00"? La manera más fácil sería crear un nuevo tipo de cuenta . La reacción aquí sería la de utilizar ellegado:
Forfait.scala |
|
No es difícil de modificar el programa de pruebas para tener en cuenta un nuevo cliente con un paquete:
Chapitre_II.scala |
|
Por último, la aplicación nos da el resultado esperado:
|
Una vez más , nada extraordinario . Y nada específico a Scala es la programación orientada a objetos todo lo que hay desde las más clásicas . Ha llegado el momento de complicar el ejemplo un poco ...
III. Ofertas modulares
Los operadores de telefonía no son personas que pueden estar satisfechos con dos ofertas . Por lo tanto, para la felicidad de sus clientes , les gusta que les ofrecen una variedad de opciones. Tal como el libre acceso a sus servicios (voz , seguido por el consuelo , ...). Y, por supuesto , esta opción debería aplicarse a diferentes tipos de suscripciones. Esto ahora nos da cuatro posibles acuerdos :
Facturación por segundos ;
de facturación de paquetes ;
de facturación en segundo lugar con el libre acceso a los servicios de operador ;
de facturación de paquetes con acceso gratuito a los servicios del operador.
Así , la forma de codificar esta en Scala ? Eso es lo que consideraremos más adelante en este artículo , teniendo en cuenta , sucesivamente, varias opciones disponibles : en primer lugar , vamos a continuar con el impulso al considerar una solución basada en elherencia simple. Luego se discute la Decorador de patrón de diseño. Y, por último , llegamos a la conclusión al ver cómo Scala proporciona el nivel de idioma con una alternativa características.
III -A. Herencia simple
Para empezar , considere la posibilidad de utilizar elherencia simple el modelo de todas estas diferentes ofertas. Después de todo , esta solución parece satisfactoria a la introducción de Paquete. Así que ¿por qué cambiar una solución que funciona ?
Como se observa en el diagrama UML anterior, agregue una única opción implica la creación de dos nuevas clases : una para los paquetes, uno para las cuentas . Ni que decir tiene que el código puede ser en gran medida redundante. Por otra parte, más allá de este ejemplo, no es difícil darse cuenta con esta solución la multiplicación de opciones conducirá a una explosión combinatoria del número de clases para ser codificado en el sistema. En resumen , esta solución conduce rápidamente a una pesadilla de mantenimiento. Este es probablemente el momento de apelar a Banda de los cuatro para ver lo que ofrecen ...
III -B. Decorador
La solución tradicional a este problema es el uso de Decorador de patrón de diseño. Con este modelo, la lógica específica de cada opción ya no está para ser codificado una vez. Y se puede utilizar para decorar cualquier autoridad de CompteAbstrait . Finalmente, una cuenta ya decoradas , que permite la combinación.
Una aplicación sería la siguiente :
DecorateurServicesGratuits.scala |
|
Chapitre_III_B.scala |
|
El diseñador puede cambiar Dinámicamente el comportamiento de un objeto durante la ejecución. Además, la aplicación de una interfaz común permite a los diseñadores para reemplazar el objeto que decora . Es esta característica que les permite combinar. Por supuesto , esto implica que el diseñador implementa todos los métodos empleados tanto del objeto detrás de ella - por lo menos como talones de que simplemente pasar el control al objeto de base. Este es el caso en el ejemplo anterior de la toString ().
Aunque posiblemente tedioso , no es un problema en el caso de la clase abstracta contiene la raíz de todos los métodos necesarios. Pero de todos modos , esto puede suponer un problema. Consideremos el caso de los paquetes con los servicios gratuitos :
|
El DecorateurServicesGratuits objeto creado y puede ser utilizado como un CompteAbstrait - porque bajo el legado es un CompteAbstrait . Por contra, los métodos y las propiedades específicas del paquete ya no son accesibles desde el exterior.
Este problema es más probable que ocurra cuando usted adorna un objeto que implementa varios interfaces. Sólo las interfaces de aplicación efectiva en el diseñador puede manipular el objeto. En la siguiente ilustración , el uso de decorador ServicesGratuits limita los métodos disponibles en elOffre3G las de la interfaz CompteAbstrait. Estos interfaz AccesInternet ya no son accesibles , aunque implementado por el objeto concreto .
En otras palabras , un diseñador no respeta el principio de sustitución de Liskov : un decorador no puede utilizarse, cuando el objeto decorado puede ser.
De alguna manera, el diseñador tiene las desventajas de sus ventajas : está diseñado para cambiar dinámicamente el comportamiento de la ejecución de una objeto. No para crear de forma estática durante la compilación nueva tipos. Sin embargo, eso es precisamente lo que permite a las características de Scala ...
III -C. Características
Scala nos ofrece la oportunidad crear nuevos tipos de compilación la combinación de los métodos y los miembros de datos de una clase con los de uno o más características. En la terminología de las clases Scala que se construyen de esta forma se llaman mixins.
La notación utilizada para crear mixins utilizando la palabra clave con como en el ejemplo siguiente. Quisiera llamar su atención sobre el hecho de que el rasgo puede ser ServicesGratuits mixto Cuenta , o un paquete :
Chapitre_III_C.scala |
|
La diferencia fundamental con el modelo de diseño decorador se ilustra en el diagrama UML arriba: se puede ver que relaciones sólo la herencia están presentes. Por lo tanto, un paquete con libre es un paquete. Eso todavía una mixin se puede utilizar siempre que sea una instancia deuna de sus clases base oUna característica mixta puede ser.
Si en este punto hemos detallado losuso un derrame cerebral, aún no hemos visto cómo los definir. Con el código de abajo se va a hacer . Como se ve , la definición de un rasgo es muy similar a una clase. La única diferencia evidente es el uso de la palabra clave línea en lugar de clase:
ServicesGratuits.scala |
|
Tenga en cuenta que la línea ServicesGratuits se extiende clase de cuenta . Pero para un accidente cerebrovascular , esta afirmación es más bien una declaración restricción: un rasgo se puede mezclar con su clase base o una clase derivada de ella. El corolario es queuna clase no puede ser combinada con una línea directa cuya matriz se encuentra en su jerarquía de elementos primarios.
Esta limitación significa que la semántica de la relación de herencia se mantiene en el nivel de objeto . De hecho, desde ServicesGratuits se puede mezclar con la cuenta o cualquiera de sus clases derivadas , se puede decir con certeza que cualquier instancia de mezcla ServicesGratuits es una instancia de la cuenta .
Es imposible mezclar el ServicesGratuits línea para alojamiento de primera clase si no es una subclase de la cuenta .
Más sutil aún , el aviso en el código de la llamada función super.impute:
ServicesGratuits.scala |
|
En el contexto de una clase, esta notación significa " llamar al método establecido en las clase base. Pero necesariamente en el caso de una línea! De hecho , cuando la definición de la línea, Scala no sabe todavía cómo va a ser mezclado clase. Por lo tanto , la clase de referencia gran se resuelve en tiempo de compilación de mixin. Por otra parte, un mixin el otro una referencia a la super en el mismo rasgo no necesariamente hará referencia a un mismo ancestro .
¿Y cómo Scala determina la clase de referencia super ? Usando una técnica llamada linealización haremos a continuación.
III- C-1. estupendo y linealización
El punto es entender que con la introducción de características, la gráfica de herencia ( implementación) no es necesariamente un árbol como lo es en Java. Para determinar qué clase o rasgo gran hará referencia a la compilación establece Scala cada mixin un orden estricto para las diferentes clases y funciones que lo componen. Es linealización.
Especificaciones de la Scala especificar algoritmo exacto utilizado . Pero al hacer simple, en el orden determinado por la linealización , un mixin está siempre delante de su cara y mezclado antes de su super - clase. Y si una clase o una sola línea aparece varias veces , sólo Pasado caso se conserva . Este último punto en particular los medios que, de acuerdo a la clase con la que un rasgo se mezcla , super no se refiere necesariamente al mismo tipo .
Más concretamente , el orden seleccionado para el mixin Paquete con ServicesGratuits es la siguiente:
Paquete con ServicesGratuits ( la propia mixin ) ;
ServicesGratuits ( accidentes cerebrovasculares son linealizadas antes de la clase base) ;
Paquete (una clase se serializa antes de su super - clase);
Cuenta
En comparación, la resolución de la cuenta con ServicesGratuits mixin es:
En ServicesGratuits con ( el propio mixin )
ServicesGratuits ( accidentes cerebrovasculares son linealizadas antes de la clase base)
Cuenta
mixin Scala linealiza cada uno independientemente . Así, el super.impute llamada en el método ServicesGratuits relacionados invoca en un caso en el Forfait.impute Compte.impute otros . Veremos en la próxima sección que podemos mezclar las características múltiples de forma simultánea y una super también podrá designar a otro derrame cerebral.
III- C -2. Mezclar varias características
Ahora vamos a abordar el corazón de esta técnica: a saber, la posibilidad de combinan varios rasgos en un mixin. Para ilustrar este punto , pulsaremos el caso de Ringo , que recibió un pase " una hora "con el libre acceso a los servicios de operador , pero en el que comunicación con el exterior de la cuenta doble embalaje. Para implementar esta última especificación , de nuevo una característica se puede utilizar :
PenalitesHorsForfait.scala |
|
El PenalitesHorsForfait sólo puede aplicarse a un paquete. Esta es la manera explícita en la declaración:
|
Así que aquí estamos con un usuario cuyo envase debe mezclar dos derrames cerebrales. Esto se escribe como en Scala :
Chapitre_III_C_2.scala |
|
Con dos golpes mezclados con el paquete de clase nuevo a la cuestión del orden en que se invocan los métodos . La regla es simple , las últimas funciones ( a cambio ) son una prioridad. Esto implica que orden de los trazos es importante!
En mi ejemplo, ServicesGratuits.impute PenalitesHorsForfait.impute suceder antes . ¿Cuál es el comportamiento deseado : en nuestra aplicación es conveniente que ServicesGratuits tiene la oportunidad de excluir a cierto sistema de facturación de comunicación antes de que el cálculo de las posibles sanciones que se hace.
|
III-D . Informe mixins
Hasta ahora , hemos características mixtas (con ...) directamente en la instancia de objeto (nueva ...) . Esto no es elegante. Una mejor opción sería la de declarar mixin una vez por todas . Luego, una instancia en caso necesario:
Chapitre_III_D.scala |
|
Chapitre_III_D.scala |
|
IV. ¿Qué rasgos ...
En este artículo , utilizamos una solución clásico sobre la base de la herencia simple de avanzar hacia la implementación de características para representar las diferentes opciones gestionados por nuestra aplicación. Para continuar con este espíritu, uno podría estar tentado a transformar clase De paquetes en línea.
Nada nos obliga a hacer . Excepto quizás el deseo de normalizar nuestra solución. Para ser honesto, todo será una oportunidad para mí presentar algunos conceptos adicionales. Pero no anticipar demasiado, y empezar con lo que ya sabemos . A priori para transformar clase De paquetes en línea, Sólo tiene que sustituir la palabra clave clase por línea:
|
El problema es que el código anterior no es válido el código Scala ! De hecho, si tratas de compilar , obtendrá un mensaje que indica :
|
A diferencia de una clase, un rasgo no puede tener parámetros. De hecho, una línea no puede tener un constructor . Y menos aún para invocar el constructor de su clase base . En resumen, a la definición de nuestra línea puede observar esta situación :
|
Esto nos plantea un problema : es necesario para determinar el crédito disponible en el paquete. Afortunadamente , Scala nos ofrece la oportunidad de evitar este problema utilizando valores abstractos. El principio es el mismo que el métodos abstractos como ustedes saben , en otros lenguajes de programación : son informó y se puede utilizado en una clase base . Pero lo harán definido en una clase derivada .
En nuestro caso, la valor lo que necesitamos es el crédito disponible en el paquete. Haremos de este valor un valor abstracto . Y será definido en el mixin:
|
Ahora que nuestra línea tiene un valor abstracto , que deben definirse al crear instancias de una clase de mezcla esta característica:
|
También es posible definir el valor de resumen en el mixin :
Chapitre_IV.scala |
|
|
Excelente, ¿no? Bueno, no tanto ! Supongamos que modificar nuestro programa un poco por la duración del contrato se expresa en hora en el momento de la creación del mismo. El cambio es trivial:
Chapitre_IV.scala |
|
|
¿Eh? ¿Qué pasó? Tenga en cuenta , en particular, el valor del paquete Imputados: 0 segundos ! La explicación de este misterio es que cuando el valor abstracto es inicializado por una expresión , se estima después de inicialización de las clases base y las características mixtas. Esto es exactamente lo contrario de lo que ocurre cuando se envía argumentos a un constructor. En este caso, los valores de los argumentos que se evalúan antes el rendimiento de distintos fabricantes. Esto es especialmente complicado, porque como vimos antes, cuando un valor abstracto se inicializa con una constante , el compilador se propaga , lo que esconde este comportamiento.
Si estas explicaciones ayudan a entender lo que pasó, sigue siendo el problema de cómo conseguir el comportamiento deseado ? Una solución es Pre- inicializar valores (en Inglés , se habla de Los primeros definiciones) . En concreto, es mostrar la inicialización de los valores de resumen en una manzana ubicada antes clase base :
Chapitre_IV.scala |
|
|
V. Conclusión
Bueno, este cuenta con la presentación en forma de apilar comportamiento se ha completado. Hemos visto aquí que los movimientos utilizados para componer una clases modulares. Por supuesto , esta técnica no es una panacea. Tiene sus propias limitaciones. Precisamente por el hecho de que mixins son creados estáticamente en tiempo de compilación . Por lo tanto , es necesario volver a compilar todos los mixins si se agrega un método a una línea. Y no podemos (fácilmente ) crear mixins nueva ejecución. Sin embargo, es una herramienta interesante para hacer que el código modular. Para agregar a su kit de herramientas si está programando en Scala !
VI. Recursos
La programación en Scala : un paso integral a paso Guía- Por Odersky Martin , Cuchara Lex y Venners proyecto de ley ( 978-0981531601 )
VII. Gracias
Un gran agradecimiento a Eric Siber para mí se propone acoger artículos en Developpez.com y reinterpretaciones de este artículo. En Damien Guichard por su interés en la Scala. También Jacques Thery por su ojo de águila me ha ahorrado un montón de conchas !
http://sylvain-leroux.developpez.com/scala/traits-scala-et-decorateurs/