Herencia

Herencia

Mecanismo para crear
una especialización de
una clase existente

Dada una clase existente, para modificar un comportamiento no
hace falta copiar/pegar código existente

Relación ES-UN

La Herencia genera alto acoplamiento; solo debe
ser usada cuando lógicamente se estable
una relación ES-UN

public class Link {
   // código
   public String getUrl() { ... }
   public String getTexto() { ... }
   public String getTarget() { ... }
   public boolean isVacío() { ... }
}
public class EmailLink extends Link {
   // código
   @Override
   public String getUrl() { 
      return "mailto:" + super.getUrl();
   }
}

Un EmailLink ES-UN
Link, es una
especialización de este

Liskov Substitution Principle- LSP

LSP

Solo utilice herencia
para relaciones ES-UN
no para reusar código

La Herencia genera
un fuerte acoplamiento entre clases, usada erróneamente puede
dañar un sistema

Rectángulo - Cuadrado

En Geometría un Cuadrado es un tipo de Rectángulo

public class Rectángulo {
   private double a;
   private double b;

   public void setA(double a) { this.a = a; }
   public void setB(double b) { this.b = b; }
   public double getA() { return a; }
   public double getB() { return b; }
}
public class Cuadrado {
   private double lado;
   // hay 2 maneras de modificar el valor 
   // de lado pero por los nombres de los 
   // métodos no es claro
   public void setA(double a) { this.lado = a; }
   public void setB(double b) { this.lado = b; }
   // igualmente, hay 2 maneras de 
   // obtener el valor de lado
   public double getA() { return lado; }
   public double getB() { return lado; }
}
void foo(Rectángulo r) {
   r.setA(5);
   r.setB(10);
   r.getA(); // debería ser 5
   r.getB(); // debería ser 10
}

* * *

foo(new Cuadradro()) // rompe las expectativas

Un método que espera
un tipo T debería funcionar correctamente con
un subtipo de T

Desde el punto de vista
de comportamiento
un Cuadrado no
ES-UN Rectángulo

Reutilización
de Código

Reutilización

¿Si la herencia no se debe usar para reutilizar código, entonces cómo se logra?

Delegación

Implementar un
método simplemente llamando a otro

public class Punto {
   private double x;
   private double y;

   public double distancia(Punto otro) {
      return Math.sqrt(Math.pow(x - otro.x), 2) 
                + Math.pow(y - otro.y), 2));
   }
}
public class Vehículo {
   private Punto punto; // composición

   public double distancia(Vehículo otro) {
      // delegación
      return punto.distancia(otro.punto);
   }
}

Prefiera composición
sobre herencia

Si un diseño acepta herencia o composición, prefiera el último

Reutilización

  • Para reutilizar código de
    la clase A en la clase B

  • Use composición para que
    la clase B tenga una instancia
    de la clase A

  • Utilice delegación

Interface

Método Abstracto

Método del cuál no se especifica su implementación

Una Interface describe
un Tipo de Dato, independientemente
de su implementación

Una Interface, en
general, sólo tiene
métodos abstractos

En Java 8 y 9 también pueden tener métodos estáticos, privados y default pero estos son por conveniencia y no porque hagan parte fundamental de lo que es una Interface

Una interface describe
el API, lo más importante de un Tipo de Dato

Una clase implementa
una Interface,
mediante herencia

Clase Abstracta

Clase que puede tener métodos abstractos

Si hay código común a varias implementaciones, este código se puede sacar a una Clase Abstracta

Link

  • Un link tiene una url, un texto y un target
    <a href="${url}" target="${target}>${texto}</a>
  • También existen email link que tienen como url expresiones como "mailto:quien@donde.com"
  • También hay image link que en vez de un
    texto tienen una imagen

Acme Framework

  • El FW que estamos usando representa
    los recursos como nodos en una
    estructura jerárquica
  • Si añadimos ".html" al path de un recursos,
    el FW nos intenta devolver una
    representación HTML de ese recurso
  • Los vínculos a los recursos siempre
    empiezan con "/content"
  • Los vínculos externos siempre se almacenan empezando por "http://" o "https://"
  • También hay recursos que son assets cuya ruta siempre empieza con "/content/assets"

¿Cómo modelamos
los link en código?

Siempre comenzamos con una Interface qué especifique qué es un Link

public interface Link {

   String getUrl();
   String getTexto();
   String getTarget();
   boolean isVacío();
}

Posibles
implementaciones
de Link

  • AcmeLink - posponer ".html"
  • FileLink - abre en el mismo tab
  • External Link - abre en otro tab
  • Email Link - preponer "mailto:"
  • Image Link - texto es una imagen

Usamos una clase
abstracta para implementar el código común

public abstract class AbstractLink implements Link {

   private String url;
   private String texto;

   public AbstractLink(String url, String texto) {
      this.url = Objects.requireNonNull(url);
      this.texto = Objects.requireNonNull(texto);
   }

   public String getUrl() {
      return url;
   }

   public String getTexto() {
      return texto;
   }

   public boolean isVacío() {
      return url.isEmpty();
   }
}

getUrl(), getTexto(), isVacío() tienen una misma implementación sin importar el tipo concreto

También las validaciones
en el constructor son comunes a todas las implementaciones

public class AcmeLink extends AbstractLink {

   public AcmeLink(String utl, String texto) {
      super(url + ".html", texto);
   }

   public getTarget() {
      return "_self";
   }
}
public class ExternalLink extends AbstractLink {

   public AcmeLink(String utl, String texto) {
      super(url, texto);
   }

   public getTarget() {
      return "_blank";
   }
}
public class EmailLink extends AbstractLink {

   public EmailLink(String utl, String texto) {
      super("mailto:" + url, texto);
   }

   public EmailLink(String url) {
      this(url, url);
   }

   public getTarget() {
      return "_blank";
   }
}
public class FileLink extends AbstractLink {

   public FileLink(String utl, String texto) {
      super(url, texto);
   }

   public FileLink(String url) {
      super(url, "download");
   }

   public getTarget() {
      return "_self";
   }
}

¿Y qué pasa con ImageLink?

Para ImageLink no tiene sentido getTexto() y si tienen métodos como getSource() y getAltText()

public class Image {
   private String source;
   private String altText;

   public Image(String source, String altText) {
     this.source = source;
     this.altText = altText;
   }

   public String getSource() {
      return source;
   }

   public String getAltText() {
      return altText;
   }

   public boolean isVacío() {
      return source.isEmpty();
   }
}
public class ImageLink {
   private Image image;
   private Link link;

   public ImageLink(Image image, Link link) {
     this.image = image;
     this.link = link;
   }

   public String getSource() { return image.getSource(); }
   
   public String getAltText() { return image.getAltText(); }
   
   public String getUrl() { return link.getUrl(); }
   
   public String getTexto() { return link.getTexto(); }
   
   public String getTarget() { return link.getTarget(); }
   
   public boolean isVacío() {
      return link.isVacío() || image.isVacío();
   }
}

LSP nos dice que un ImageLink no ES-UN link

Interface Segregation Principle - ISP

ISP

Ninguna implementación debería ser obligada
a depender de métodos que no usa

La esencia del principio
es evitar que las
interfaces tengan demasiados métodos

TreeSet es: una colección, cuyos elementos pueden recorrerse, que no permite repetidos, cuyos elementos están ordenados, y permite búsquedas por rangos

En vez de tener una sola interface, implementa 5: Collection, Iterable, Set, SortedSet, NavigableSet

Dependency Inversion
Principle - DIP

DIP

  • Módulos de alto nivel, no
    deben depender de módilos
    de bajo nivel. Ambos deben
    depender de abstracciones.

  • Abstracciones no deben
    depender de detalles. Los
    detalles solo deben
    depender de abstracciones.

Abstracciones = Interfaces

En general se deberían
usar Interfaces para:
valores de retornos, parámetros de métodos públicos y atributos

La razón debería ser clara: da flexibilidad en
la implementación
que utilizamos

/**
 * Este método acepta cualquier tipo
 * que sea una List, no importa cual
 */
public void foo(List<String> data) {
   // code
}
/**
 * Este método retorna una List.
 * La implementación puede cambiar,
 * pero no importa porque seguirá
 * siendo una List
 */
public List<String> foo() {
   // code
}
public class Foo {
  /**
   * Esta clase se implementa con una List
   * Cual List es un detalle de 
   * implementación que puede cambiar 
   * en cualquier momento.
   */
   private List<String> data;
   // code
}

Para los atributos,
esto se combina con Dependency Injection Principle

public class ImageLink {
   private Image image;
   private Link link;

   public ImageLink(Image image, String url, String texto) {
      // la implementación es fija
      // no hay flexibilidad
      this.link = new ExternalLink(url, texto);
      this.image = image;
   }
}
public class ImageLink {
   private Image image;
   private Link link;

   public ImageLink(Image image, Link link) {
      // la implementación es inyectada
      // la responsabilidad es del
      // código cliente
      this.link = link;
      this.image = image;
   }
}

Árbol de Herencia

Árbol de Herencia

La lista jerárquica de
los clases de las que
hereda una clase

interface Link {}
abstract class AbstractLink implements Link {}
class SimpleLink extends AbstractLink {}
class EmailLink extends SimpleLink {}

El árbol de herencia de Link es: Link, AbstractLink, SimpleLink e EmailLink

Q&A

Herencia

By Carlos Obregón

Herencia

La segunda etapa en La Travesía del Programador es: Herencia

  • 1,965