Commits

Miguel Gordian  committed 941133e

Herencia, refinamiento e interfaces

Se agrega el capitulo de herencia, refinamiento e interfaces la cual es la quinta
entrega del tour de Ceylon.

  • Participants
  • Parent commits 39511cc

Comments (0)

Files changed (1)

File source/herencia_refinamiento_interfaces.rst

+====================================
+Herencia, refinamiento, e interfaces
+====================================
+
+Esta es la cuarta parada de el Tour de Ceylon. En la parte anterior hemos visto
+atributos, variables, setters, y estructuras de control. En esta sección 
+aprenderemos acerca de Herencia y refinamiento(conocido como "overriding" en 
+muchos lenguajes).
+
+La herencia es uno de dos caminos que Ceylon nos permite para abstraer los 
+tipos. (La otra son `generics`, los cuales veremos mas adelante en este tour.)
+Una característica de Ceylon es un tipo de herencia múltiple llamada `"mixin 
+inheritance"`. Tal vez has escuchado o experimentado la herencia múltiple 
+en C++. Pero `mixini inheritane` en Ceylon mantiene una seria de restricciones
+que mantienen un buen balance entre poder y inocuidad.
+
+-----------------------
+Herencia y refinamiento
+-----------------------
+
+En programación orientada a objetos, frecuentemente remplazamos condicionales
+(if, switch) con subtipos. De hecho, acuerdo a algunas personas, esto es lo
+que hace un programa orientado a objetos. Tratemos de re-factorizar la clase
+`Polar` de la anterior parada del tour en dos clases, con dos diferentes
+implementaciones de `description`. Aquí esta la super clase:
+
+.. code:: ceylon
+    
+    "Una coordenada polar"
+    class Polar(Float angulo, Float radio) {
+        
+        shared Polar rotar(Float rotacion) => 
+            Polar(angulo+rotacion, radio);
+
+        shared Polar dilatar(Float dilatacion) =>
+            Polar(angulo, radio*dilatacion);
+
+        "La descripcion por default"
+        shared default String descripcion => 
+            "(``radio``,``angulo``)";
+    }
+
+Notese que Ceylon fuerza a que declaremos atributos o metodos que pueden
+ser refinados(overriden) anotándolos con `default`.
+
+Subclases especifican su super-clase usando la parabra reservada `extends`,
+seguida por el nombre de la super-clase, seguida por una lista de argumentos
+a ser enviados al inicializador de la super-clase. Parece solo una expresión
+que instancia una super-clase:
+
+.. code:: ceylon
+    
+    "Una coordenada polar con una etiqueta"
+    class EtiquetaPolar(Float angulo, Float radio, String etiqueta)
+            extends Polar(angulo, radio) {
+            
+        "La descripcion con etiqueta"
+        shared actual String descripcion => 
+            etiqueta  + "-" + super.descripcion;
+    }
+
+Ceylon también nos fuerza a declarar que atributo o método(override)
+un atributo o método de una super-clase anotándola con `actual` (no
+`override` en Java). Todas estas anotaciones tienen un pequeño 
+costo extra al teclear, pero ayudan al compilador a detectar errores. 
+No podemos inadvertidamente refina un miembros de una super-clase, o
+inadvertidamente fallar al refinarla.
+
+Note que Ceylon deja fuera este camino al repudiar la idea del
+"duck" tipyng o structural typing. Si el `camina()` como un `Pato`,
+entonces el deberá de ser un subtipo de `Duck` y deberá de refinar
+explícitamente la definición de `caminar()` en `Pato`. Nosotros no creemos
+que solamente el nombre de un método o atributo es suficiente para 
+identificar su semántica. Y mas importante, "structural typing" no
+trabaja adecuadamente con herramientas.
+
+################################
+Sintaxis corta para refinamiento
+################################
+
+Existe una manera mas compacta para refinar un miembro `default` de una
+super-clase simplemente especificando su refinada implementación usando =>, 
+como en el siguiente ejemplo:
+
+.. code:: ceylon
+    
+    "Una coordenada polar con una etiqueta"
+    class EtiquetaPolar(Float angulo, Float radio, String etiqueta)
+            extends Polar(angulo, radio) {
+            
+        descripcion => etiqueta  + ": " + super.descripcion;
+    }
+
+O asignado un valor usando =, como el siguiente ejemplo:
+
+.. code:: ceylon
+    
+    "Una coordenada polar con una etiqueta"
+    class EtiquetaPolar(Float angulo, Float radio, String etiqueta)
+            extends Polar(angulo, radio) {
+            
+        descripcion = "``etiqueta``: (``radio``, ``angulo``)";
+    }
+
+Puedes refinar cualquier función o cualquiera que no sea `variable`
+usando esta sintaxis.
+
+##############################
+Refinando un miembro de Object
+##############################
+
+Nuestra clase `Polar` es un subtipo implicito de la clase `Object` en el
+paquete `ceylon.language`. Si quieres mirar dentro de esta clase, podrás 
+ver que tiene un atributo `default` llamado `string`. Es común refinar
+este atributo para proveer a un desarrollador una amigable reprensentación
+del objeto.
+
+Nuestra clase `Polar` es también un subtipo de la interfaz `Identifiable`
+la cual define implementaciones `default` de `equals()` y `hash()`. Tambien
+necesitamos refinar estos:
+
+.. code:: ceylon
+    
+    "Una coordenada polar"
+    class Polar(Float angulo, Float radio) {
+        
+
+        // ...
+
+        shared default String descripcion => 
+            "(``radio``,``angulo``)";
+
+        value azimuth => pi * (angulo/pi).fractionalPart;
+
+        shared actual Boolean equals(Object that){
+            if (is Polar that) {
+                return azimuth==that.azimuth &&
+                       radio==that.radio;
+            }
+            else {
+                return false;
+            }
+        }
+
+        shared actual Integer hash => radio.hash;
+
+        shared actual String string => description;
+    }
+    
+Esta es la primera vez que hemos visto esta sintaxis:
+
+.. code:: ceylon
+    
+    if (is Polar that) { ... }
+
+Como probablemente supusiste, `if (is ...)` trabaja como `if (exists ...)`,
+probando y estrechando el tipo de un valor. En este caso prueba el tipo de `that`
+y lo estrecha a `Polar` si `that` es una instancia de `Polar`. Volveremos después 
+en el tour a esta construcción.
+
+Usando la sintaxis corta para refinamiento que hemos visto, podemos abreviar el 
+código anterior como esto:
+
+
+.. code:: ceylon
+    
+    "Una coordenada polar"
+    class Polar(Float angulo, Float radio) {
+        
+
+        // ...
+
+        shared default String descripcion => 
+            "(``radio``,``angulo``)";
+
+        value azimuth => pi * (angulo/pi).fractionalPart;
+
+        shared actual Boolean equals(Object that){
+            if (is Polar that) {
+                return azimuth==that.azimuth &&
+                       radio==that.radio;
+            }
+            else {
+                return false;
+            }
+        }
+
+        hash => radio.hash;
+
+        string => descripcion;
+    }
+
+
+(Pero en este caso, esta sintaxis corta es quizás no una mejora.)
+
+#################
+Clases abstractas
+#################
+
+Ahora consideremos un problemas mas interesante: Una abstracción sobre
+el sistema de coordenadas polar y cartesiana. Desde que una coordenada
+no es solo un tipo especial de coordenadas polares, esto es un caso
+para la introducción de una super-clase abstracta:
+
+.. code:: ceylon
+    
+    "Una libre abstraccion de un sistema de coordenadas para un punto
+    geometrico"
+    abstract class Point() {
+    
+        shared formal Polar polar;
+        shared formal Cartesiano cartesiano;
+
+        shared formal Punto rotar(Float  rotacion);
+        shared formal Punto dilatar(Float dilatacion);
+
+    }
+
+Ceylon requiere que anotemos las clases abstractas con `abstract`, al igual
+que en Java. Esta anotación  indica que una clase no puede ser instanciada 
+y puede definir miembros abstractos. Como en Java, Ceylon también requiere
+que anotemos con `abstract` los miembros a los cuales no especifiquemos una
+implementación. Sin embargo, la anotación requerida en `formal`. La razón
+para tener dos distintas anotaciones, como veremos mas adelante, es que
+clases anidadas pueden ser `abstract` o `formal`, y las clases anidadas
+`abstract` son un poco diferentes a una clase miembro `formal`. Una clase
+miembro `formal` puede ser instanciada; una clase `abstract` no puede.
+
+Note que un atributo que nunca es inicializado es siempre un atributo `formal`.
+Ceylon Ceylon no inicializa los atributos a cero o `null` amenos que tu lo
+especifiques.
+
+Una manera de definir una implementación para una atributo abstracto heredado 
+es usar la sintaxis corta de refinamiento que hemos visto anteriormente.
+
+.. code:: ceylon
+    
+    "Una coordenada polar"
+    class Polar(Float angulo, Float radio)
+            extends Punto() {
+            
+        polar => this;
+        
+        cartesiano => Cartesiano(radio*cos(angulo), radio*sin(angulo));
+
+        rotar(Float rotacion) => Polar(angulo+rotacion, radio);
+        
+        dilatar(Float dilatacion) => Polar(angulo, radio*dilatacion);
+            
+    }
+
+Alternativamente, podemos escribirlo de la manera larga.
+
+.. code:: ceylon
+    
+    "Una coordenada polar"
+    class Polar(Float angulo, Float radio)
+            extends Punto() {
+            
+        shared actual Polar polar => this;
+
+        shared actual Cartesiano cartesiano => 
+                Cartesiano(radio*cos(angulo), radio*sin(angulo));
+
+        shared actual Polar rotar(Float rotacion) => 
+                Polar(angulo+rotacion, radio);
+        
+        shared actual Polar dilatar(Float dilatacion) => 
+                Polar(angulo, radio*dilatacion);
+            
+    }
+
+Note que Ceylon, como Java, permite el refinamiento co-variante del
+tipo de un miembro. Refinamos el tipo de retorno de `rotar()` y
+`dilatar`, estrechando a `Polar` desde el tipo mas general declarado
+por `Punto`. Pero Ceylon actualmente no soporta refinamiento 
+contra variante de los tipos de un parámetro. No puedes refinar un 
+método y ensanchar el tipo de un parámetro. (Algún día nos gustaría
+arreglar esto.)
+
+Por supuesto, no puedes refinar un miembro y ensanchar el tipo de
+retorno, o cambiar a algún tipo arbitrario diferente, desde que en
+dicho caso la subclase  deberá no ser a lo largo un subtipo de el
+supertipo. Si tu vas a refinar el tipo de retorno, tienes que 
+refinarlo a un subtipo.
+
+`Cartesiano` también de mannera covariante refina `rotar()` y `dilatar()`,
+pero a un tipo diferente de retorno.
+
+.. code:: ceylon
+    
+    import ceylon.math.float { atan }
+
+    "Una coordenadas cartesiana"
+    class Cartesiano(Float x, Float y)
+            extends Punto() {
+            
+        shared actual Polar polar => Polar( (x^2+y^2)^0.5, atan(y/x) );
+
+        shared actual Cartesiano cartesiano => this;
+
+        shared actual Cartesiano rotar(Float rotacion) =>
+                polar.rotar(rotacion).cartesiano;
+
+        shared actual Cartesiano dilatar(Float dilatacion) =>
+                Cartesiano(x*dilatacion, y*dilatacion);
+
+    }
+
+No hay una manera de prevenir que otro código extienda una clase (no hay un equivalente
+una clase final in Java). Desde que únicamente miembros que son declarados para soportar
+refinamiento usando alguno de `formal` o `default` puede ser refinado, un subtipo no
+puede romper la implementación de un supertipo. A menos que el supertipo fuese 
+explícitamente diseñado a ser extendido, un subtipo puede agregar métodos, pero nunca
+cambiar el comportamiento de los miembros heredados.
+
+Las clases abstractas son útiles. Pero desde que las interfaces en Ceylon son mas 
+poderosas que las interfaces en Java, es mas frecuente usar una interfaz en vez 
+de una clase abstracta.
+
+--------------------------------
+Interfaces y herencia "múltiple"
+--------------------------------
+
+Algunas veces, nos surge alguna situación  donde una clase necesita de funcionalidades
+anidadas de mas de un supertipo. El modelo de herencia de Java no soporta esto, desde 
+que una interfaz no puede  define un miembro con una implementación concreta. Las 
+interfaces en Ceylon son un poco más flexibles:
+
+- Una interfaz puede definir un método concreto, getter y setters de atributos, pero
+- Puede no definir referencia o inicialización lógica.
+
+Note que prohibiendo las referencias y la inicialización lógica crea interfaces 
+completamente sin estado.
+Una interfaz no puede mantener referencia a otros objetos.
+
+Tomemos ventaja de herencia "mixin" para definir una interfaz `Writer` para Ceylon.
+
+.. code:: ceylon
+    
+    interface Writer {
+    
+        shared formal Formatter formatter;
+
+        shared formal void write(String string);
+
+        shared void writeLine(String string) {
+            write(string);
+            write("\n");
+        }
+
+        shared void writeFormattedLine(String format, Object* args) {
+            writeLine(formatter.format(format, args));
+        }
+    }
+
+
+Note que no podemos definir un valor concreto para el atributo `formatter`,
+desde que una interfaz no puede mantener una referencia a otro objeto.
+
+Ahora definamos una concreta implementación de la interfaz.
+
+.. code:: ceylon
+    
+    class ConsoleWriter() satifies Writer {
+        formatter => StringFormatter();
+        write(String string) => print(string);
+    }
+
+La palabra reservada `satisfies` (no implements como en Java) es usada para
+especificar que una interfaz extiende a otra interfaz o que una clase
+implementa una intefaz. A diferencia de la declaración `extends`, una 
+declaración `satisfies` no se le especifican argumentos , puesto que las
+interfaces no tienen parámetros o inicialización logica. Ademas, la
+declaración `satisfies` puede declarar mas de una interfaz.
+
+El enfoque de Ceylon en las interfaces elimina un patrón común en Java donde
+una clase abstracta separada define una implementación por default para
+algún miembro de la interfaz. En Ceylon, la implementación por default puede
+ser especificada por la interfaz misma. Incluso mejor, es posible agregar un
+nuevo miembro a una interfaz sin romper una implementación existente de la 
+interfaz.
+
+
+#################################
+Ambigüedad en herencia "múltiple"
+#################################
+
+Es ilegal para un tipo heredar dos miembros con el mismo nombre, amenos que 
+ambos miembros(directa o indirectamente) refinen a un común miembros de un
+común supertipo, y el tipo heredado también refine el miembro para eliminar
+cualquier ambigüedad. Lo siguiente resulta en un error de compilación:
+
+.. code:: ceylon
+    
+    interface Party {
+        shared formal String legalName;
+        shared default String nombre => legalName;
+    }
+
+    interface User {
+        shared formal String userId;
+        shared default String name => userId;
+    }
+
+    class Customer(String customerName, String email)
+            satisfies User & Party {
+            
+        shared actual String legalName = customerName;
+        shared actual String userId = email;
+        shared actual String name = customerName;  // error: refina dos diferentes
+                                                   // miembros
+    }
+
+Para arreglar este código, deberá factorizar una declaración `formal` de el atributo
+`name` a un común supertipo.
+El siguiente código es legal.
+
+.. code:: ceylon
+    
+    interface Named {
+        shared formal String name;
+    }
+
+    interface Party satisfies Named {
+        shared formal String legalName;
+        shared actual default String name => legalName;
+    }
+
+    interface User satisfies Named {
+        shared formal String userId;
+        shared formal default String name => userId;
+    }
+
+    class Customer(String customerName, String email)
+            satisfies User & Party {
+        shared actual String legalName = customerName;
+        shared actual String userId = email;
+        shared actual String name = customerName;        
+    }
+
+Oh, y por supuesto lo siguiente es ilegal:
+
+
+.. code:: ceylon
+    
+    interface Named {
+        shared formal String name;
+    }
+
+    interface Party satisfies Named {
+        shared formal String legalName;
+        shared actual String name => legalName;
+    }
+
+    interface User satisfies Named {
+        shared formal String userId;
+        shared formal String name => userId;
+    }
+
+    class Customer(String customerName, String email)
+            satisfies User & Party { // error: inherits multiple definitions
+                                     // of name
+        shared actual String legalName = customerName;
+        shared actual String userId = email;
+    }
+
+Para arreglar este código, `name` deberá de ser declarado `default` en ambos
+`User` y `Party` y explícitamente refinado en `Customer`.
+
+"""""""""""
+Aun hay mas
+"""""""""""
+
+En la siguiente parada, para finalizar la discusión de programación orientada 
+a objetos en Ceylon, aprenderemos acerca de clases anónimas y clases miembro.