Skip to content

Latest commit

 

History

History
402 lines (306 loc) · 11 KB

iiss-oop-2021.md

File metadata and controls

402 lines (306 loc) · 11 KB
marp
<style> h1 { text-align: center; } h2 { color: darkblue; text-align: center; } </style>

IMPLEMENTACIÓN E IMPLANTACIÓN DE SISTEMAS SOFTWARE

<style scoped> h2 { text-align: left; } </style>
  1. Objetos
  2. Aspectos
  3. Contratos
  4. Funciones
  5. Eventos

OBJETOS - Principios

Caso 1 - Recorrido de listas

Ocultación de la implementación

Versión inicial: Lista v0.1

Abstracción: La clase abstracta List<T> diferencia entre el qué y el cómo: Qué hace la lista vs. cómo se almacenan los elementos


Criticar la implementación siguiente:

  public abstract class List<T> {
    public void addFirst(T value) { ... };
    public void removeFirst() { ... };
    public void addLast(T value) { ... };
    public void removeLast() { ... };
    public T first() { ... };
    public T last() { ... };
    public boolean isEmpty() { ... };
    public int length() { ... };
    public List<T> clone() { ... };
    public boolean isEqualTo(List<T>) { ... };
    public abstract void traverse();
    // etc...
  }

Cohesión

Cohesion refers to the degree to which the elements inside a module belong together

-- E. Yourdon & L. Constantine

Críticas a Lista v0.1
  • List<T> aglutina más de una responsabilidad: almacenar y recorrer. Implementación no cohesionada
  • ¿Y si hay distintas implementaciones de traverse()? Si implementamos varias versiones de la lista, introducimos más dependencias (acoplamiento)
Problemáticas de Lista v0.1
  • Baja cohesión
  • Alta variabilidad no bien tratada --> poca flexibilidad

Implementación alternativa: Lista v0.2

Delegar funcionalidad hacia las subclases (vía herencia).

Criticar la implementación:

  class ListForward<T> extends List<T> {
    //...
    public void traverse() { // recorrer hacia adelante };
  }
  class ListBackward<T> extends List<T> {
    //...
    public void traverse() { // recorrer hacia atras};
  }

Críticas a Lista v0.2
  • ¿Qué operación hace traverse() con cada elemento individual (imprimir, sumar, etc.)? ¿Hay que especializar de nuevo para cada tipo de operación?
  • ¿Y si hay que especializar de nuevo el recorrido: sólo los pares, sólo los impares, etc.?
Problemáticas de Lista v0.2
  • Elevada complejidad
  • Alta variabilidad no bien tratada --> poca flexibilidad, mala reutilización

Implementación alternativa: Lista v0.3

Ampliamos la interfaz...

  public interface List<T> {
    public void addFirst(T value);
    public void removeFirst();
    public void addLast(T value);
    public void removeLast();
    public T first();
    public T last();
    public boolean isEmpty();
    public int length();
    public List<T> clone();
    public boolean isEqualTo(List<T>);
    public void traverseForward();
    public void traverseBackWard();
    public void traverseEvens(); //pares
    public void traverseOdds();  //impares
    // etc...
  }

Críticas a Lista v0.3
  • Si hay que cambiar la operación básica que hace traverse() con cada elemento (imprimir, sumar, etc.), ¿cuántos métodos hay que cambiar? Hay muchas dependencias
  • Cuanto más variedad de recorridos (la interfaz es mayor), menos flexibilidad para los cambios. Implementación poco flexible
Problemáticas de Lista v0.3
  • Muchas dependencias --> acoplamiento
  • Poca flexibilidad

Implementación alternativa: Lista v0.4

Delegar hacia otra clase

  public interface List<T> {
    void addFirst(T value);
    void removeFirst();
    void addLast(T value);
    void removeLast();
    T first();
    T last();
    boolean isEmpty();
    int length();
    List<T> clone();
    boolean isEqualTo(List<T>);
    Iterator<T> iterator();
  }

  public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
  }

Ventajas
  • Mayor cohesión: Las responsabilidades están ahora separadas: List almacena, Iterator recorre. List está más cohesionada
  • Uso de delegación: la responsabilidad de recorrer se ha delegado hacia otro sitio

Ocultar la implementación

  • Cohesión: módulos auto-contenidos, independientes y con un único propósito
  • Acoplamiento: minimizar dependencias entre módulos
  • Abstracción: diferenciar el qué y el cómo
  • Modularidad: clases, interfaces y componentes/módulos

Alta cohesión, bajo acoplamiento

Cuando los componentes están aislados, puedes cambiar uno sin preocuparte por el resto. Mientras no cambies las interfaces externas, no habrá problemas en el resto del sistema

-- Eric Yourdon


Modularidad

Reducir el acoplamiento usando módulos o componentes con distintas responsabilidades, agrupados en bibliotecas


Técnicas de ocultación

Hay diversas técnicas para ocultar la implementación...

  • Encapsular: agrupar en módulos y clases
  • Visibilidad: public, private, protected, etc.
  • Delegación: incrementar la cohesión extrayendo funcionalidad pensada para otros propósitos fuera de un módulo
  • Herencia: delegar en vertical
  • Polimorfismo: ocultar la implementación de un método, manteniendo la misma interfaz de la clase base
  • Interfaces: usar interfaces bien documentadas

Herencia: generalización y especialización

  • Reutilizar la interfaz

    • Clase base y derivada son del mismo tipo
    • Todas las operaciones de la clase base están también disponibles en la derivada
  • Redefinir vs. reutilizar el comportamiento

    • Overriding (redefinición): cambio de comportamiento
    • Overloading (sobrecarga): cambio de interfaz
  • Herencia pura vs. extensión

    • Herencia pura: mantiene la interfaz tal cual (relación es-un)
    • Extensión: amplía la interfaz con nuevas funcionalidades(relación es-como-un). Puede causar problemas de casting.

When you inherit, you take an existing class and make a special version of it. In general, this means that you’re taking a general-purpose class and specializing it for a particular need. [...] it would make no sense to compose a car using a vehicle object —a car doesn’t contain a vehicle, it is a vehicle. The is-a relationship is expressed with inheritance, and the has-a relationship is expressed with composition.

-- Bruce Eckel


Ejemplo: Aventura v0.1

   public class PersonajeDeAccion {
     public void luchar() {}
   }

   public class Heroe extends PersonajeDeAccion {
     public void luchar() {}
     public void volar() {}
   }

   public class Creador {
     PersonajeDeAccion[] personajes() {
       PersonajeDeAccion[] x = {
         new PersonajeDeAccion(),
         new PersonajeDeAccion(),
         new Heroe(),
         new PersonajeDeAccion()
       };
       return x;
     }
   }

   public class Aventura {
     public static void main(String[] args) {
       PersonajeDeAccion[] cuatroFantasticos = new Creador().personajes();
       cuatroFantasticos[1].luchar();
       cuatroFantasticos[2].luchar(); // Upcast

       // En tiempo de compilacion: metodo no encontrado:
       //! cuatroFantasticos[2].volar();
       ((Heroe)cuatroFantasticos[2]).volar(); // Downcast
       ((Heroe)cuatroFantasticos[1]).volar(); // ClassCastException
       for (PersonajeDeAccion p: cuatroFantasticos)
           p.luchar; // Sin problema
       for (PersonajeDeAccion p: cuatroFantasticos)
           p.volar; // El 0, 1 y 3 van a lanzar ClassCastException
     }
   }

Críticas a Aventura v0.1
  • ¿De qué tipos van a ser los personales de acción? --> problema de downcasting
  • Hay que rediseñar la solución por ser insegura

   interface SabeLuchar {
     void luchar();
   }
   interface SabeNadar {
     void nadar();
   }
   interface SabeVolar {
     void volar();
   }
   class PersonajeDeAccion {
     public void luchar() {}
   }
   class Heroe
       extends PersonajeDeAccion
       implements SabeLuchar,
                  SabeNadar,
                  SabeVolar {
     public void nadar() {}
     public void volar() {}
   }

   public class Aventura {
     static void t(SabeLuchar x)
        { x.luchar(); }
     static void u(SabeNadar x)
        { x.nadar(); }
     static void v(SabeVolar x)
        { x.volar(); }
     static void w(PersonajeDeAccion x)
        { x.luchar(); }
     public static void main(String[] args)
     {
       Heroe i = new Heroe();
       t(i); // Tratar como un SabeLuchar
       u(i); // Tratar como un SabeNadar
       v(i); // Tratar como un SabeVolar
       w(i); // Tratar como un PersonajeDeAccion
     }
   }

Uso correcto de la herencia

Hay dos formas de contemplar la herencia:

  • Como tipo:

    • Las clases son tipos y las subclases son subtipos
    • Las clases satisfacen la propiedad de substitución (LSP, Liskov Substitution Principle): toda operación que funciona para un objeto de la clase C también debe funcionar para un objeto de una subclase de C
  • Como estructura:

    • La herencia se usa como una forma cualquiera de estructurar programas
    • Esta visión es errónea, pues provoca que no se satisfaga la propiedad LSP

Ejemplo: herencia como estructura
class Account {
  float balance;
  float getBalance() { return balance; }
  void transferIn (float amount) { balance -= amount; }
}

class VerboseAccount extends Account {
  void verboseTransferIn (float amount) {
    super.transferIn(amount);
    System.out.println("Balance: "+balance);
  };
}

class AccountWithFee extends VerboseAccount {
  float fee = 1;
  void transferIn (float amount) { super.verboseTransferIn(amount-fee); }
}

  • Todos los objetos $a$ de la clase Account deben cumplir que si $b=a.getBalance()$ antes de ejecutar $a.transferIn(s)$ y $b´=a.getBalance()$ después de ejecutar $a.transferIn(s)$, entonces $b+s=b´$.
  • Sin embargo, con la estructura AccountWithFee < VerboseAccount < Account, un objeto de tipo AccountWithFee no funciona bien cuando se contempla como un objeto Account. Considérese la siguiente secuencia:
void f(Account a) {
  float before = a.getBalance();
  a.transferIn(10);
  float after = a.getBalance();
  // Suppose a is of type AccountWithFee:
  //   before + 10 != after    !!
  //   before + 10-1 = after
}

Polimorfismo

Fenómeno por el que, cuando se llama a una operación de un objeto del que no se sabe su tipo específico, se ejecuta el método adecuado de acuerdo con su tipo.

El polimorfismo se basa en:

  • Enlace dinámico: se elige el método a ejecutar en tiempo de ejecución, en función de la clase de objeto; es la implementación del polimorfismo

  • Moldes (casting)

    • Upcasting: Interpretar un objeto de una clase derivada como del mismo tipo que la clase base
    • Downcasting: Interpretar un objeto de una clase base como del mismo tipo que una clase derivada suya