|
| 1 | +Métodos Estáticos y por defecto en Interfaces |
| 2 | +-------- |
| 3 | + |
| 4 | +Todos entendemos que deberíamos programar con interfaces. Los interfaces dan al cliente un contrato que deberían usar sin preocuparse en los detalles de la implementación ( p.e. las clases). Por lo tanto, fomentan **[el bajo acoplamiento](https://en.wikipedia.org/wiki/Loose_coupling)**. Diseñar interfaces limpios es uno de los aspectgos más importantos en el diseño de APIs. Uno de los principios SOLID **[Segregación de interfaces](https://en.wikipedia.org/wiki/Interface_segregation_principle)** habla sobre diseñar interfaces específicos para el cliente más pequeños en vez de un interfaz más genérico. El diseño de interfaces es la clave para tener APIs limpios y efectivos para nuestas librerías y aplicaciones. |
| 5 | + |
| 6 | +> El código de esta sección está en [ch01 package](https://github.com/shekhargulati/java8-the-missing-tutorial/tree/master/code/src/main/java/com/shekhargulati/java8_tutorial/ch01). |
| 7 | +
|
| 8 | +Si has diseñado algún API, con el tiempo, habrás sentido la necesidad de añadirle nuevos métodos. Una vez que se publica el API se hace imposible añadir métodos a un interfaz sin romper las implementaciones existentes. Para aclarar este punto vamos a suponer que estamos desarrollando un API sencillo de una calculadora `Calculator` que soporta las operaciones de sumar `add`, restar `subtract`, dividir `divide` y multiplicar `multiply`. Podemos escribir el interfaz `Calculator`como se muestra abajo. ***Para hacerlo sencillo usaremos enteros.*** |
| 9 | + |
| 10 | +```java |
| 11 | +public interface Calculator { |
| 12 | + |
| 13 | + int add(int first, int second); |
| 14 | + |
| 15 | + int subtract(int first, int second); |
| 16 | + |
| 17 | + int divide(int number, int divisor); |
| 18 | + |
| 19 | + int multiply(int first, int second); |
| 20 | +} |
| 21 | +``` |
| 22 | + |
| 23 | +Para respaldar este interfaz `Calculator` desarrollaste la implementación de `BasicCalculator` como se muestra abajo. |
| 24 | + |
| 25 | +```java |
| 26 | +public class BasicCalculator implements Calculator { |
| 27 | + |
| 28 | + @Override |
| 29 | + public int add(int first, int second) { |
| 30 | + return first + second; |
| 31 | + } |
| 32 | + |
| 33 | + @Override |
| 34 | + public int subtract(int first, int second) { |
| 35 | + return first - second; |
| 36 | + } |
| 37 | + |
| 38 | + @Override |
| 39 | + public int divide(int number, int divisor) { |
| 40 | + if (divisor == 0) { |
| 41 | + throw new IllegalArgumentException("El divisor no puede ser cero."); |
| 42 | + } |
| 43 | + return number / divisor; |
| 44 | + } |
| 45 | + |
| 46 | + @Override |
| 47 | + public int multiply(int first, int second) { |
| 48 | + return first * second; |
| 49 | + } |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +## Métodos de Factoría Estáticos |
| 54 | + |
| 55 | +El API calculadora resultó ser muy útil y fácil de usar. Los usuarios sólo tienen que crear una instancia de `BasicCalculator` y ya pueden usar el API. Comienzas a ver código como el que se muestra abajo. |
| 56 | + |
| 57 | +```java |
| 58 | +Calculator calculator = new BasicCalculator(); |
| 59 | +int sum = calculator.add(1, 2); |
| 60 | + |
| 61 | +BasicCalculator cal = new BasicCalculator(); |
| 62 | +int difference = cal.subtract(3, 2); |
| 63 | +``` |
| 64 | + |
| 65 | +¡Vaya! Los usuarios del API están usando la implementación del API en vez de su interfaz `Calculator`. Como la clase `BasicCalculator` era pública, tu API no obligaba a los usuarios a usar los interfaces. Si haces tu paquete `BasicCalculator` protegido tendrías que ofrecer una clase factoría estática que se dedicará a proveer la implementación de `Calculator`. Vamos a mejorar el código para manejar esto. |
| 66 | + |
| 67 | +Primero, haremos el paquete `BasicCalculator` protegido así los usuarios no podrán acceder a la clase directamente. |
| 68 | + |
| 69 | +```java |
| 70 | +class BasicCalculator implements Calculator { |
| 71 | + // El resto permanece igual |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +Luego, escribiremos una clase factoría que nos facilite la instancia de `Calculator` como se muestra abajo. |
| 76 | + |
| 77 | +```java |
| 78 | +public abstract class CalculatorFactory { |
| 79 | + |
| 80 | + public static Calculator getInstance() { |
| 81 | + return new BasicCalculator(); |
| 82 | + } |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +Ahora, los usuarios se verán obligados a usar el interfaz `Calculator` y no tendrán acceso a los detalles de la implementación. |
| 87 | + |
| 88 | +Aunque hemos logrado nuestra meta hemos aumentado el tamaño de nuestra API añadiendo una nueva clase `CalculatorFactory`. Ahora los usuarios del API tendrán que aprender una clase más antes de usar eficazmente nuestro API. Esta era la única solución disponible antes de Java 8. |
| 89 | + |
| 90 | +**Java 8 te permite declarar métodos estáticos dentro de un interfaz**. Esto permitirá a los diseñadores de APIs definir métodos de utilidad estáticos como `getInstance` en el propio interfaz y, por lo tanto, mantener el API sencillo y corto. Los métodos estáticos dentro de un interfaz se podrían usar para sustituir las clases de asistencia <i>helpers</i> estáticas (`CalculatorFactory`) que normalmente creamos para definir métodos de ayuda asociados a un tipo. Por ejemplo, la clase `Collections` es una clase de ayuda que define varios métodos de asistencia para trabajar con colecciones e interfaces asociadas. Los métodos definidos en la clase `Collections` podrían ser añadidos facilmente a `Collection` o a cualquiera de sus interfaces hijos. |
| 91 | + |
| 92 | +El código de abajo se puede mejorar en Java 8 añadiendo un método estático `getInstance` en el propio interfaz `Calculator`. |
| 93 | + |
| 94 | +```java |
| 95 | +public interface Calculator { |
| 96 | + |
| 97 | + static Calculator getInstance() { |
| 98 | + return new BasicCalculator(); |
| 99 | + } |
| 100 | + |
| 101 | + int add(int first, int second); |
| 102 | + |
| 103 | + int subtract(int first, int second); |
| 104 | + |
| 105 | + int divide(int number, int divisor); |
| 106 | + |
| 107 | + int multiply(int first, int second); |
| 108 | + |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +## La Evolución del API con el tiempo |
| 113 | + |
| 114 | +Algunos de los consumidores decidieron o bien, ampliar el API `Calculator` añadiendo métodos como resto `remainder`, o escribit su propia implementación del interfaz `Calculator`. Tras hablar con tus usuarios sacaste la conclusión de que a la mayoría de ellos les gustaría tener un método `remainder` en el interfaz `Calculator`. Parecía un cambio muy simple al API por lo que añadiste un método nuevo. |
| 115 | + |
| 116 | +```java |
| 117 | +public interface Calculator { |
| 118 | + |
| 119 | + static Calculator getInstance() { |
| 120 | + return new BasicCalculator(); |
| 121 | + } |
| 122 | + |
| 123 | + int add(int first, int second); |
| 124 | + |
| 125 | + int subtract(int first, int second); |
| 126 | + |
| 127 | + int divide(int number, int divisor); |
| 128 | + |
| 129 | + int multiply(int first, int second); |
| 130 | + |
| 131 | + int remainder(int number, int divisor); // Nuevo método añadido al API |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +Añadir un método rompió la compatibilidad origen del API. Esto quiere decir que los usuarios que implementaron el interfaz `Calculator` tendrían que añadir el método `remainder` o su código no compilará. Este es un grave problema para los diseñadores de APIs ya que hace que la evolución de los mismos sea complicada. Antes de Java 8, no era posible tener implementación de métodos en los interfaces, lo que es un problema cuando se requiere ampliar un API, p.e. añadiendo uno o más métodos a la definición de la interfaz. |
| 136 | + |
| 137 | +Para permitir a los APIs evolucionar con el tiempo, Java 9 permite a los usuarios proporcionar implementaciones por defecto a métodos definidos en el interfaz. Estos se llaman métodos por defecto **<i>default</i>** o de defensa **<i>defender</i>**. La clase que implementa el interfaz no necesita proporcionar implementación para estos métodos. Si la clase que implementa el interfaz proporciona la implementación del método entonces se usará esta en vez de la implementación por defecto del interfaz. El interfaz `List` tiene definidos algunos métodos por defecto como `replaceAll`, `sort` y `splitIterator`. |
| 138 | + |
| 139 | +```java |
| 140 | +default void replaceAll(UnaryOperator<E> operator) { |
| 141 | + Objects.requireNonNull(operator); |
| 142 | + final ListIterator<E> li = this.listIterator(); |
| 143 | + while (li.hasNext()) { |
| 144 | + li.set(operator.apply(li.next())); |
| 145 | + } |
| 146 | +} |
| 147 | +``` |
| 148 | + |
| 149 | +Podemos resolver nuestro problema del API definiendo un método por defecto como se muestra abajo. Los métodos por defecto se definen normalmente usando métodos ya existentes -- `remainder` se define usando los métodos `subtract`, `multiply` y `divide`. |
| 150 | + |
| 151 | +```java |
| 152 | +default int remainder(int number, int divisor) { |
| 153 | + return subtract(number, multiply(divisor, divide(number, divisor))); |
| 154 | +} |
| 155 | +``` |
| 156 | + |
| 157 | +## Herencia múltiple |
| 158 | + |
| 159 | +Una clase puede extender sólo una clase pero puede implementar múltiples interfaces. Ahora que es posible tener implementación de métodos en interfaces Java tiene herencia múltiple de comportamiento. Java ya tenía herencia múltiple a nivel de tipo y ahora tambén a nivel de comportamiento. Existen tres reglas de resolución que ayudan a decidir que método será elegido: |
| 160 | + |
| 161 | +**Regla 1: Los métodos declarados en las clases tendrán preferencia sobre los definidos en las interfaces.** |
| 162 | + |
| 163 | +```java |
| 164 | +interface A { |
| 165 | + default void doSth(){ |
| 166 | + System.out.println("Dentro de A"); |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +class App implements A{ |
| 171 | + |
| 172 | + @Override |
| 173 | + public void doSth() { |
| 174 | + System.out.println("Dentro de App"); |
| 175 | + } |
| 176 | + |
| 177 | + public static void main(String[] args) { |
| 178 | + new App().doSth(); |
| 179 | + } |
| 180 | +} |
| 181 | +``` |
| 182 | + |
| 183 | +Esto imprimirá `Dento de App` ya que los métodos declarados en la clase tienen prioridad sobre los métodos declarados en el interfaz. |
| 184 | + |
| 185 | +**Regla 2: En otro caso, se eligirá el interfaz más específico** |
| 186 | + |
| 187 | +```java |
| 188 | +interface A { |
| 189 | + default void doSth() { |
| 190 | + System.out.println("Dentro de A"); |
| 191 | + } |
| 192 | +} |
| 193 | + |
| 194 | +interface B {} |
| 195 | + |
| 196 | +interface C extends A { |
| 197 | + default void doSth() { |
| 198 | + System.out.println("Dentro de C"); |
| 199 | + } |
| 200 | +} |
| 201 | + |
| 202 | +class App implements C, B, A { |
| 203 | + |
| 204 | + public static void main(String[] args) { |
| 205 | + new App().doSth(); |
| 206 | + } |
| 207 | +} |
| 208 | +``` |
| 209 | + |
| 210 | +Esto imprimirá `Dentro de C`. |
| 211 | + |
| 212 | +**Regla 3: Sino, la clase tiene que llamar explicitamente a la implementación que desea** |
| 213 | + |
| 214 | +```java |
| 215 | +interface A { |
| 216 | + default void doSth() { |
| 217 | + System.out.println("Dentro de A"); |
| 218 | + } |
| 219 | +} |
| 220 | + |
| 221 | +interface B { |
| 222 | + default void doSth() { |
| 223 | + System.out.println("Dentro de B"); |
| 224 | + } |
| 225 | +} |
| 226 | + |
| 227 | +class App implements B, A { |
| 228 | + |
| 229 | + @Override |
| 230 | + public void doSth() { |
| 231 | + B.super.doSth(); |
| 232 | + } |
| 233 | + |
| 234 | + public static void main(String[] args) { |
| 235 | + new App().doSth(); |
| 236 | + } |
| 237 | +} |
| 238 | +``` |
| 239 | +Esto imprimirá `Dentro de B`. |
| 240 | + |
| 241 | +[](https://github.com/igrigorik/ga-beacon) |
0 commit comments