sintesis

¿Por qué?

  • Inefectividad, ineficacia y/o ineficiencia, del Proyecto Software

    • porque tiene malas variables en su economía:

      • ámbito incumplido y/o

      • tiempo incumplido y/o

      • coste incumplido;

    • porque tiene mala calidad del software, fiabilidad, usabilidad, interoperatividad, seguirdad, …​ (-ilities)

    • porque tiene mala mantenibilidad

      • viscoso, porque no se puede entender con facilidad y/o

      • rígido, porque no se puede cambiar con facilidad y/o

      • frágil, porque no se puede probar con facilidad y/o

      • inmovil, porque no se puede reutilizar con facilidad y/o

    • porque tiene complejidad arbitraria

davidProyectoImplantacionDiseñoMal

arbolNoMantenible

Diseño modular no asegura un código calidad

Una única clase asume la responsabilidad de toda la jerarquía

personaGorda
  • Baja cohesión: incumpliendo el Principio de Única Responsabilidad

  • Clase grande: o propensa a ser grande con métodos largos cuando lleguen nuevos subtipos por la Ley del Cambio Continuo junto con "no hay 2 sin 3"

Existe una clase por cada tipo de elemento que asumen sus correspondientes responsabilidades

hombreMujer
  • Alto acoplamiento: de los clientes de la jerarquía porque conocen a todas las clases descencientes

  • DRY: con código repetidos en distintas clases a mantener, documentar, probar, …​

Existe una jerarquía de clases por cada tipo de elemento que asumen sus correspondientes responsabilidades

personaHombreMujer
  • Alto acoplamiento: de los clientes de la jerarquía porque conocen a todas las clases descencientes

  • Existen ciclos entre las clases base y descendientes de la jerarquía, lo que complica la legibilidad, pruebas, extensibilidad, resuabilidad, …​

¿Qué?

OOP para mí significa solo mensajería, retención local, protección y ocultación del estado del proceso, y vinculación tardía extrema de todas las cosas. Se puede hacer en Smalltalk y en LISP. Posiblemente hay otros sistemas en los que esto es posible, pero no los conozco.
— Alan Kay
Meaning of “Object-Oriented Programming”
Mi conjetura es que la orientación a objetos será en los 80 lo que la programación estructurada en los 70. Todo el mundo estará a favor suyo. Cada productor prometerá que sus productos lo soportan. Cada director pagará con la boca pequeña el servirlo. Cada programador lo practicará. Y nadie sabrá exactamente lo que es!
— Rentsch
Object-Oriented Programming. SIGPLAN Notices vol. 17(12). 1982
X es bueno. Orientado a Objetos es bueno. Ergo, X es Orientado a Objetos
— Stroupstrup
The C++ Programming Language. 1988

Programación Orientada a Objetos = Clases + Herencia

Herencia Polimorfismo
  • para aportar más reusabilidad

  • para aportar más flexibilidad

  • Transmisión de todo miembro, atributos y métodos, de una clase base a sus clases derivadas, como un copy+paste dinámico porque si cambio la clase base repercute a todas las clases derivadas

  • Especialización, de la clase derivada mediante las posibilidades que muestra la siguiente tabla:

CRUD Atributo Método

Añadir nuevo

Correcto

Correcto

Modificar transmitido

Incorrecto

Correcto

Eliminar transmitido

Incorrecto

Incorrecto

  • Relajación del sistema de tipos donde la dirección de un objeto a una clase puede ser sustituida por la dirección a un objeto de cualquier clase derivada pero los mensajes disponibles se ciñen a la interfaz de la clase de la referencia o puntero que alberga la dirección

    • Para disfrutar de mensajes correspondientes a métodos añadidos por alguna clase derivada se exige el downcast a la clase del objeto de la dirección polimórfica, con el consecuente peligro de error de ejecución si la clase del objeto polimórfico no es la clase del downcast o derivada

Teoría de Lenguajes

  • Los lenguajes de programación tienen diversos elementos: clases, sentencia iterativa 0..N, un tipo primitivo, un cierre (clousure), un parámetro, …​ dependiendo del paradigma del lenguaje

    • Cada elemento tiene distintas caracterísiticas: nombre, dirección, tamaño, …​ con distintos valores, identificador, hexadecimal, entero, …​

  • un método tiene un nombre, una secuencia de parámtros (cada uno con sus características), un cuerpo y un valor de retorno

  • una constante tiene un nombre, un tipo, un valor, …​

  • una variable tiene un nombre, un tipo, un valor, una dirección, …​

Enlace estático Enlace dinámico

enlace que se puede resolver en tiempo de compilación, el valor de la característica se puede determinar mirando el código

enlace que solo se puede resolver en tiempo de ejecución, el valor de la característica se puede determinar en un instante de la ejecución del código

  • final int MAX = 10;

    • nombre: MAX // estático

    • tipo: int // estático

    • valor: 10 // estático

    • …​

  • int age;

    • nombre: age // estático

    • tipo: int // estático

    • valor: ¿? // dinámico

    • dirección: ¿? // dinámico

    • …​

  • final int MAX = 10;

    • nombre: MAX // estático

    • tipo: int // estático

    • valor: 10 // estático

    • …​

  • int age;

    • nombre: age // estático

    • tipo: int // estático

    • valor: 666 ó 0 ó …​ // dinámico

    • dirección: ¿0h a FFFFFFFFFFFFFFFFh? // dinámico

    • …​

Datos polimórficos

  • ¿Cuál es el tipo de enlace, estático o dinámico, entre una expresión (elemento del lenguaje) y el tipo del valor del resultado de su evaluación (característica del elemento del lenguaje)?

  • Por tanto, se define el polimorfismo como enlace dinámico de expresiones al tipo devuelto por su evaluación

  • Atención: No se pregunta por "¿Cuál es el tipo de enlace, estático o dinámico, entre una expresión (elemento del lenguaje) y el valor del resultado de su evaluación (característica del elemento del lenguaje)?" cuya respuesta obviamente es un enlace dinámico!

Sin polimorfismo Con polimorfismo

Enlace estático, para toda expresión puede deducirse el tipo del valor del resultado de su evaluación en tiempo de compilación

Enlace dinámico, existen expresiones para las que no puede deducirse el tipo del valor del resultado de su evaluación en tiempo de compilación

Incluso con sobrecarga pero restringida para varios métodos con el mismo nombre y con diferentes parámetros, en número y/o tipos correspondientes, para evitar la ambigüedad con el tipo del valor de retorno

Con cualquier expresión que devuelva la dirección a un objeto declarada a una clase base, dado que el tipo del objeto referenciado puede ser de la clase base, si no es abstracta, o de cualquiera de sus descendientes, por la "relajación del sistema de tipos" del polimorfismo

sobrecargaDatos
polimorfismoDatos

Operaciones polimórficas

  • ¿Cuál es el tipo de enlace, estático o dinámico, entre un mensaje (elemento del lenguaje) y el método correspondiente a su ejecución (característica del elemento del lenguaje)?

  • Se define el polimorfismo como enlace dinámico de mensajes a métodos ejecutados

Sin polimorfismo Con polimorfismo

Enlace estático, un mensaje lanzado a un objeto ejecutará el método con el mismo nombre y parámetros, en número y tipos correspondientes, de la clase del objeto

Enlace dinámico, un mensaje lanzado a un objeto polimórfico ejecutará un método con el mismo nombre y parámetros, en número y tipos correspondientes, de alguna de las clases de la jerarquía a partir de la clase base de la referencia

Incluso con sobrecarga pero restringida para varios métodos con los mismos parámetros, en número y tipos correspondientes, para evitar la ambigüedad con el tipo del valor de retorno

Con cualquier expresión que devuelva una referencia/puntero a una clase base, dado que el tipo del objeto referenciado puede ser de la clase base, si no es abstracta, o de cualquiera de sus descendientes, por la "relajación del sistema de tipos" del polimorfismo

sobrecargaOperaciones
polimorfismoOperaciones

¿Para qué?

  • Efectividad, eficacia y eficiencia, del Proyecto Software

    • porque tiene buenas variables en su economía:

      • ámbito cumplido y

      • tiempo cumplido y

      • coste cumplido;

    • porque tiene buena calidad del software, fiabilidad, usabilidad, interoperatividad, seguirdad, …​ (-ilities)

    • porque tiene buena mantenibilidad

      • fluido, porque se puede entender con facilidad y/o

      • flexible, porque se puede cambiar con facilidad y/o

      • fuerte, porque se puede probar con facilidad y/o

      • reusable, porque se puede reutilizar con facilidad y/o

    • porque tiene complejidad inherente

davidProyectoImplantacionDiseño

arbolMantenible]

Fluido Flexible Resuable Robusto

Presencia de multitud de clases pequeñas con métodos pequeños con pequeños acoplamientos acíclicos que puedo recorrer de arriba abajo (top/down o bottom/up), jerarquía de composición y/o clasificación de clases pequeñas, sin ciclos!

Reparto de responsabilidades equilibrado y centralizado en clases que requiere modificarse únicamente si cambian los requisitos correspondientes, jerarquías de clases con alta cohesión, sin ciclos!

Presencia de multitud de clases pequeñas, cohesivas y poco acopladas a tecnologías, algoritmos, …​!

Presencia de red de seguridad de pruebas unitarias por posibilidad de realizar pruebas sobre las clases anteriores …​ jerarquía equilibrada de clases pequeñas con alta cohesión y bajo acoplamiento!

Principio Abierto/Cerrado

  • Open/Close Principle (OCP) en 1988

    • Definido por Bertran Meyer

    • Incluido por Robert Martin como

      • segundo de los principios S*O*LID

openClose
Motivación Solución
  • Se debería diseñar módulos que nunca cambien. Cuando los requisitos cambian, se extiende el comportamiento de dichos módulos añadiendo nuevo código

    • Si hay un nuevo tipo de extensión no afecta a todos los clientes de la jerarquía y, así, no "romper" la versión actual que funciona: Si no está roto, no lo toques!

  • Las entidades de software (módulos, clases, métodos, …) deberían estar abiertas a la extension pero cerradas a la modificación,

  • La forma normal de extender el comportamiento de un módulo es hacer cambios a ese módulo. Un módulo que no puede ser cambiado, se piensa normalmente que tendrá un comportamiento fijo. Parece que estos dos atributos están en conflicto entre sí.

    • Usando los principios de la programación orientada a objetos, es possible crear abstracciones que son fijas y a la vez representan un grupo ilimitado de posibles comportamientos. Las abstracciones son clases base abstractas y el ilimitado grupo de posibles comportamientos es representado por todos las posibles clases derivadas.

    • Es posible para un módulo manipular una abstracción.

      • Tal módulo puede ser cerrado para la modificación si depende de una abstracción que es fija.

      • Todavía el comportamiento del módulo puede ser extendido creando nuevas derivadas de la abstracción.

Violaciones Contraindicaciones:
  • Usar atributos que no sean privados

  • Usar variables globales

  • Consultar el tipo de objeto polimórfico

  • …​

  • Debería estar claro que no significa que un programa sea 100% cerrado. En general, no es la cuestión cómo cerrar un módulo, habrá siempre alguna clase de cambio para la cual no está cerrado

    • Dado que el cierre no puede ser completo, debe ser una estrategia. Estos es, los diseñadores deben elegir la clase de cambios contra los cuales cerrar el diseño. Esto toma cierta cantidad de presciencia derivada de la experiencia. Los diseñadores experimentados conocen a los usuarios y la industria suficientemente bien para juzgar la probabilidad de diferentes clases de cambios.

    • Debe asegurarse que el Principio Abierto/Cerrado es aplicado para los cambios más probables: YAGNI!

¿Cómo?

Clasificación

Clases Abstractas e Interfaces

- Clases Abstractas - Interfaces
  • Aquellas clases que no son instanciables porque tienen algún método abstracto, sin definición, lo cuál imposibilita su instanciación ante la ejecución de mensajes correspondientes a métodos abstractos sin definción

  • Son clases abstractas puras, sin definición de atributos y métodos, solo cabeceras de los métodos abstractos, la interfaz de la clase

  • Facilitan la reusabilidad, de todos los atributos y métodos definidos

  • No aportan reusabilidad

  • Facilitan la flexibilidad mediante el polimorfismo que aporta una relajación del sistema de tipos sobre una jerarquía de herencia

    • Sin la clase base abstracta no hay herencia, no hay posible polimorfismo de datos

    • Sin el método abstracto no hay interfaz, no hay posible polimorfismo de operaciones

  • Facilitan la flexibilidad mediante el polimorfismo que aporta una relajación del sistema de tipos sobre una jerarquía de herencia

    • Sin la clase base abstracta pura no hay herencia, no hay posible polimorfismo de datos

    • Sin los métodos abstractos no hay interfaz, no hay posible polimorfismo de operaciones

Aportan abstracción/encapsulación, modularidad, bajo acoplamiento, cohesión!!!

Código Sucio por Herencia Rechazada

Justificación Solución
  • Las subclases heredan los métodos y atributos de sus padres que no necesitan.

  • La solución gira en torno a crear clases intermedias en la jerarquía, habitualmente abstractas, mover métodos y atributos hacia arriba y hacia abajo hasta que todas las subclases reciban los métodos y atributos necesarios y no más. De tal manera que cada clase padre tenga el factor común de sus clases derivadas.

  • A menudo, esta solución complica la jerarquía en exceso. En tal caso, si la herencia rechazada es la implantación “vacía” de un método de la clase derivada podría considerarse como solución frente a la complicación de la jerarquía. Pero si la herencia rechazada es la transmisión de métodos públicos implantados a clases derivadas que no lo necesitan, debería re-diseñarse la jerarquía de herencia por delegación para evitar corromper la interfaz de la clase derivada

Ley Flexible y Estricta de Demeter

Sinónimos Synonyms Libro Autor

No hablescon extraños

Do not talk to strangers

Lieberherr

Cadena de Mensajes

Chain of Message

Smell Code (Refactoring)

Martin Fowler

Ley estricta de Demeter Ley flexible de Demeter
  • Justificación: controlar el bajo acoplamiento restringieno a qué objetos enviar mensajes desde un método

  • Enviar mensajes únicamente a:

    • this y super

    • Atributos de la clase

    • Parámetro del método

    • Local del método

    • No enviar nunca a otros objetos indirectos obtenidos como resultado de un mensaje a un objeto de conocimiento directo.

  • Enviar mensajes únicamente a:

    • this y super

      • Atributos de la clase y de la clase base

      • Parámetro del método

      • Local del método

        • No enviar nunca a otros objetos indirectos obtenidos como resultado de un mensaje a un objeto de conocimiento directo, incluso siendo transmitidos desde la clase base

Reusabilidad

Patrón Método Plantilla

Sinónimos Synonyms Libro Autor

Método Plantilla

Template Method

Patrones de Diseño

Gamma et al

Justificación Solución
  • Se dificulta la extracción de un factor común en los códigos de los métodos de las clases derivadas por detalles inmersos en el propio código pero respetando un esquema general

  • Definir el esqueleto de un algoritmo de un método, diferir algunos pasos para las clases derivadas.

    • El patron permite que las clases derivadas redefinan esos pasos abstractos sin cambiarla estructura del algoritmo de la clase padre

  • Sin método plantilla

  • Con método plantilla

class X {

}

class Y extends X {
public void m() {
  // aaaaaaaaaaaa
  // yyyyyyyyyyyy
  // bbbbbbbbbbbb
}

}

class Z extends X {
public void m() {
  // aaaaaaaaaaaa
  // zzzzzzzzzzzz
  // bbbbbbbbbbbb
}

}
class X {
public void m() {
  // aaaaaaaaaaaa
  this.middle();
  // bbbbbbbbbbbb
}

public abstract middle();
}

class Y extends X {
public middle(){
  // yyyyyyyyyyyy
}

}

class Z extends X {
public middle(){
  // zzzzzzzzzzzz
}

}
sinPlantilla
conPlantilla

Herencia vs Composición

Composición Herencia

Reusabilidad por ensamblado

Reusabilidad por extensión

class Todo {

 private Parte parte;

 public Todo() {
   this.parte = new Parte();
 }

 public void m4(){
   ...
   this.parte.m1();
   this.parte.m3();
   ...
 }

 public void m5(){
   ...
 }

 public void m1(){
   ...
   this.parte.m1();
   ...
 }

 public void m2(){
   ...
 }

}

class Parte {
 public Parte() { ... }
 public void m1() { ... }
 public void m2() { ... }
 public void m3() { ... }
}
class Base {
 public Base() { ... }
 public void m1() { ... }
 public void m2() { ... }
 public void m3() { ... }
}

class Descendiente extends Base {

 public Descendiente() {
   super();
 }

 public void m4(){
   ...
   this.m1();
   this.m3();
   ...
 }

 public void m5(){
   ...
 }

 public void m1(){
   ...
   super.m1();
   ...
 }

 public void m2(){
   ...
 }

}
esquemaClasesComposicion
esquemaClasesHerencia
esquemaObjetosComposicion
esquemaObjetosHerencia

Reusabilidad con desarrollo explícito en el código, declarando los atributos que son parte del todo y enviando mensajes para su gestión

Reusabilidad implícita en el lenguaje, declarando la herencia se transmiten automáticamente todos los atributos y métodos, públicos, protegidos, privados y de paquete, con unos accesos u otros dependiendo del punto de vista (clase cliente o clase descendiente)

Más objetos para la reusabilidad, se tiene un objeto para el todo y otro para la parte y, por tanto, menos eficiente por la re-emisión de mensajes del objeto "todo" al objeto "parte"

Menos objetos para la reusabilidad, se tiene un objeto de la clase descendiente y, por tanto, más eficiente por la emisión directa de mensajes al objeto "descendiente"

Relación dinámica entre objetos, por tanto es más flexible porque un todo puede colaborar con distintas partes en el tiempo creando nuevos objetos

Relación estática entre clases, por tanto es menos flexible porque un objeto de la clase descendiente es y será un objeto de la clase descendiente sin poder modificar su clase base en tiempo de ejecución

Caja negra, desde la clase todo se tiene acceso a miembros públicos de la parte, sin posibilidad de modificar el código reusado

Caja blanca, desde la calse descendiente se tiene acceso a miembros públicos y protegidos y de paquete, con posiblidad de modificar el código reusado mediante la redefinción (@Override)

Fácil de mantener porque es imposible romper el principio de encapsulación

Difícil de mantener porque es fácil romper el principio de encapsulación

Herencia vs Parametrización

  • La parametrización es para aquellos casos en que la variabilidad se ciñe al tipo de elemento que tratan entre varias clases

  • Con la parametrización o genericidad se evita re-codificar, re-probar, re-documentar, …​, re-mantener todas las clases cuando hay nuevos tipos de listas o cuando hay que modificar el comportamiento de todas las pilas por un diseño alternativo o error

  • el código que diferencia las clases para una lista de cerdos y una lista de deseos, se ciñe únicamente al tipo de elemento, cerdo o deseo

  • un distribuidor de tareas y un distribuidor de informes, se ciñe únicamente al tipo de elemento, tarea o informe

Lista de cerdos Lista de deseos Lista genérica
class PigPile {
 private Pig[] pigs;
 private int top;
 private int size;

public PigPile(int size){
  this.pigs = new Pig[size];
  this.top = 0;
 }

 public void push(Pig pig){
  assert pig != null;
  assert this.top+1 < this.pigs.length
  this.pigs[this.top] = pig;
  this.top++;
 }

public Pig pop(){
  assert this.top>0;
  this.top--;
  return this.pigs[this.top];
 }

public boolean empty(){
  return this.top==0;
}

}

class Pig {
...
}

PigPile pigs = new PigPile(3);
class WishPile {
 private Wish[] wishes;
 private int top;
 private int size;

public WishPile(int size){
  this.wishes = new Wish[size];
  this.top = 0;
 }

 public void push(Wish wish){
  assert wish != null;
  assert this.top+1 < this.wishes.length
  this.wishes[this.top] = wish;
  this.top++;
 }

public Wish pop(){
  assert this.top>0;
  this.top--;
  return this.wishes[this.top];
 }

public boolean empty(){
  return this.top==0;
}

}

class Wish {
...
}

WishPile wishes = new WishPile(1000);
class Pile<E> {
 private E[] items;
 private int top;
 private int size;

public Pile(int size){
  this.items = new E[size];
  this.top = 0;
 }

 public void push(E item){
  assert item != null;
  assert this.top+1 < this.items.length
  this.items[this.top] = item;
  this.top++;
 }

public E pop(){
  assert this.top>0;
  this.top--;
  return this.items[this.top];
 }

public boolean empty(){
  return this.top==0;
}

}

class Pig {
...
}

Pile<Pig> pigs = new Pile<Pig>(3);

class Wish {
...
}

Pile<Wish> wishes = new Pile<Wish>(1000);
pilaCerdos
pilaDeseos
pilaParametrizada

Flexiblidad

Principio de Sustitución de Liskov

  • Definido por Barbara Liskov

    • Liskov’sSustitutionPrinciple, LSP

    • incorporado por Robert Martin

      • como uno de los principios SO*L*ID

Lo que se quiere aquí es algo como la siguiente propiedad de sustitución: si para cada objeto oT de un tipo T, hay un objeto oS de tipo S tal que para todo progama P definido en términos de T, el comportamiento de P no cambia cuando oT es sustituido por oS, entonces S es un subtipo de T
— Barbara Liskov
A behavioral notion of subtyping. ACM Transactions on Programming Languages and Systems (TOPLAS). Volume 16. Issue 6 (November 1994). pp. 1811. 1841
preLiskov
postLiskov
Justificación Violaciones
  • Se cumple sólo cuando los tipos de derivados son totalmente sustituibles por sus tipos base de forma que las funciones que utilizan estos tipos base pueden ser reutilizados con impunidad y los tipos derivados se puede cambiar con impunidad.

  • El Principio de Sustituciónde Liskov dice que las funciones que usan punteros o referencias a una clase base debe ser capaz de usar los objetos de las clases derivadas sin conocerlas

  • Por tanto, la relación de herencia se refiere al comportamiento. No al comportamiento privado intrínseco si no al comportamiento público extrínseco del que dependen los clientes

  • Una de las violaciones más evidentes de este principio es el uso de la Información de Tipos en Tiempo de Ejecución(instantceof, RTTI, …) para seleccionar una función basada en el tipo de un objeto. Muchos ven esta estructura como el anatema de la Programacion Orientada a Objetos.

    • Cuando se considera si un diseño particular es apropiado o no, no se debe simplemente ver la solución aislada. Uno debe verlo en términos de las asunciones razonables que serán hechas por los usuarios de este diseño.

Se cumple cuando se redefine un método en una derivada reemplazando su precondición por una más débil y su postcondicion por una más fuerte
— Barbara Liskov
Principio de Sustitución
  • La precondición de un subtipo es creada combinando con el operador OR: la precondicion es del tipo base y del subtipo, lo que resulta una precondición menos restrictiva.

  • La postcondición de un subtipo es creada combinando con el operador AND: la postcondiciones del tipo base y del subtipo, lo que resulta una postcondición más restrictiva.

sinLiskov
Aplicaciones

Herencia vs Delegación

Motivación Solución
  • En una jerarquía de herencia a veces se fuerza a incorporar métodos únicamente por el beneficio de una de sus subclases. Esta práctica es indeseable por que cada vez que una clase derivada necesite un nuevo método, éste será añadido a la clase base. Esto va a contaminar aún más la interfaz de la clase base, por lo que sería poco cohesiva.

  • Además, cada vez que un nuevo interfaz se añade a la clase base, éste debe ser implementado (o permitido por defecto) en las clases derivadas. De hecho, una práctica asociada es añadir estos interfaces a la clase base con métodos “vacíos” más que con métodos abstractos, así las clases derivadas no son agobiadas con su necesaria implementación; lo cual viola el principio de sustitución de Liskov

  • Cualquier relación de herencia puede convertirse en una relación de composición o delegación

Relación de Herencia Relación de Composición para Delegación
herenciaClasesDelegado
composiciónClasesDelegado
Relación de Herencia Relación de Composición para Delegación
secuenciaHerencia
secuenciaDelegado
Aplicaciones

Código Sucio por Jerarquías Paralelas de Herencia

Justificación Solución
  • Es un caso especial de la Cirugía a Escopetazos.

  • Cada vez que haces una subclase de una clase, también tienes que hacer una subclase de otra. Se reconoce por los prefijos en los nombres de clases de la jerarquía son los mismos que los prefijos de la otra jerarquía

  • Reestructurar la jerarquía:

    • Reubicar de Reparto de Responsabilidades

    • Aplicar el Patrón Método Plantilla

    • Una clase contiene tantos roles polimórficos como cada una de las jerarquía paralelas en los que delega y combina su comportamiento

pacientesDuplicado
pacientesRol
pacientesRoles

Técnica de Doble Despacho

  • Por un reparto de responsabilidades justificado, existe la necesidad de que un cliente de una jerarquía de clases trate específicamente según la clase derivada concreta de un parámetro polimórifco

  • la secretaría de alumnos atiende de forma distinta para la matriculación de distintos tipos de alumnos: master en Cloud Apps, master en Ingeniería Web, …​, grados, ESA (para adultos), Erasmus (del Espacio Europeo), invitado, …​; un profesor para evaluar con distintos tipos de pruebas según el nivel del tipo de alumno concreto; …​

1ª solución: preguntando por el tipo del objeto polimórfico
  • Ver código

    • De forma directa con operadores y funciones del lenguaje, instanceOf, o indirectamente con métodos explícitos, get<Tipo>(), o …​ abriendo distintas ramas de sentencias alternativas para tratar cada tipo de clase derivada.

restauranteClasesInstanceOf
restauranteSecuenciaInstanceOf
  • Consecuencias:

    • viola el Principio de Sustitución de Liskov preguntando por el tipo de objeto polimórfico

    • incurre en cambios divergentes para atender con una nueva rama en cada clase cliente que hay que localizar por toda la aplicación

    • rompe el principio Open/Close con cambios en el interior de los métodos del cliente

2ª solución: aplicando la técnica de doble despacho
  • Ver código

    • El cliente envia un mensaje aceptar al objeto polimórfico auto-pasándose como parámetro (this)

    • Cada clase derivada devuelve un mensaje visitar al propio cliente auto-pasándose como parámetro (this)

    • El cliente atiende por separado con métodos visitar para cada tipo de clase derivada con el comportamiento particular para cada uno

restauranteSecuenciaDobleDesapacho
restauranteClasesDobleDesapacho
  • Consecuencias:

    • no viola el Principio de Sustitución de Liskov preguntando por el tipo de objeto polimórfico

    • no incurre en cambios divergentes para atender con una nueva rama en cada clase cliente

    • no rompe el principio Open/Close con cambios en el interior de los métodos del cliente

    • intimidad inapropiada con ciclos entre todas las clases cliente con todas las clases de la jerarquía

    • alto acoplamiento según tienden a crecer los clientes porque todas las clases de la jerarquía conocen a todas las clases de clientes

3ª solución: principio abierto/cerrado
  • Ver código

    • La jerarquía de clases no conoce directamente a los clientes sino que conoce únicamente a una interfaz que cumple todo cliente que visita la jerarquía, visitador genérico.

      • la nueva clase derivada debe redefinir el método aceptar para no ser abstracta enviando un mensaje visitar auto-pasandose por parámetro

      • los cambios están guiados por el compilador porque cada clase cliente debe definir un nuevo método visitar para la nueva clase derivada

restauranteClasesInversionDependencias
  • Consecuencias:

    • no viola el Principio de Sustitución de Liskov preguntando por el tipo de objeto polimórfico

    • no incurre en cambios divergentes para atender con una nueva rama en cada clase cliente

    • no rompe el principio Open/Close con cambios en el interior de los métodos del cliente

    • con "leve" intimidad inapropiada con ciclos dentro del mismo paquete entre todas las clases de la jerarquía con la interfaz de los clientes, que no requiere pruebas ni comprensión porque no aporta código de implementación

    • bajo acoplamiento según tienden a crecer los clientes porque no todas las clases de la jerarquía conocen a todas las clases de clientes, solo conocen a la interfaz de todos los clientes

Aplicaciones

Inversión de Control

Sinónimos Synonyms

Principio Hollywood: “No me llames, ya te llamaremos”

Hollywood Principle: "Don’t call me, we’ll call you"

  • La Inversión de Control es una parte fundamental de lo que hace un framework diferente a una biblioteca.

    • Una biblioteca es esencialmente un conjunto de funciones que se pueden llamar, en estos días por lo general organizados en clases. Cada llamada que hace un poco de trabajo y devuelve el control al cliente.

    • Un framework encarna algún diseño abstracto, con un comportamiento más integrado. Para utilizarlo es necesario insertar comportamiento en varios lugares en el framework ya sea por subclases o por conectar sus propias clases. El código del framework después llama a ese código en estos puntos.

Una característica importante de un framework es que los métodos definidos por el usuario para adaptar el framework, a menudo se llaman desde dentro del propio framework, en lugar desde el código de la aplicación del usuario. El framework a menudo desempeña el papel del programa principal en la coordinación y secuenciación de actividad de la aplicación. Esta inversión de control da al framework el poder para servir como esqueletos extensibles. Los métodos suministrados por el usuario se adaptan a los algoritmos genéricos definidos en el framework para una aplicación particular
— Johonson & Foote
1988
“algunas personas confunden el principio general de Inversión de Control con los estilos específicos de Inversión de Control, como la Inyección de Dependencias, que estos contenedores utilizan”
— Fowler
  • Variaciones:

    • Patrón Método Plantilla con redefinición de métodos abstractos invocados desde el método plantilla de la clase base

    • Eventos con auditores que determinan su comportamiento

    • Configuración con datos externos al framework para determinar el comportamiento

    • Inyección de Dependencias

Inyección de Dependencias

Sinónimos Synonyms Libro Autor

Inyección de Dependencias

Pluggin

Patrón de Diseño Estrategia

Strategy Pattern Design

Patrones de Diseño

Gamma et al

Justificación Solución
  • Eliminar las dependencias de una clase de aplicación hacia la implementación de otra clase, servicio, para que esta clase sea reutilizada por implementaciones alternativas del servicio actual

    • El problema persiste mientras la clase instancie un objeto concreto

  • Lo primero será que la clase trabaje con un interfaz del servicio para que éste pueda ser extendido por otras implementaciones de servicio diferentes. Pero, aunque la clase guarde la referencia al objeto servicio a través de un interfaz, mientras la clase instancie directamente el objeto servicio, continuará el acoplamiento indeseado que impide la reutilización.

  • Lo segundo será inyectar el servicio concreto a la clase a través de la interfaz de tal manera que, por la abstracción del polimorfismo, desconoce con qué servicio concreto está trabajando. Alternativas para la inyección del objeto será:

    • por constructor, muy recomendable cuando sea posible, asociar a objetos válidos en el momento de la construcción

    • por métodos setter, cuando por constructor se complica por la cantidad de parámetros, muchas formas de construcción, cuando se desea cambiar dinámicamente el proveedor del servicio durante la vida del objeto al que se le inyectó, …​

  • Por último, para la creación e inyección del servicio a la clase:

    • para aplicaciones que pueden desplegarse en muchos lugares, un archivo de configuración tiene más sentido.

    • para aplicaciones sencillas que no tienen demasiada variación en el despliegue, es más fácil usar código para el ensamblado de los componentes.

Sin Inyección de Dependencias Con Inyección de Dependencias por constructor Con Inyección de Dependencias por método
class OneReport {

private Element[] elements;
private OneChecker checker;

public OneReport(Element[] elements){
  this.elements = elements;
  this.checker = new OneChecker();
}

public void generate() {
  ...
  for(Element element : this.elements){
    if (checker.isChecked(element)){
      ...
    }
  }
  ...
}

}

class OneChecker {
  boolean isChecked(Element element){
    return ...
  }

}

class OtherReport {

private Element[] elements;
private OhterChecker checker;

public OtherReport(Element[] elements){
  this.elements = elements;
  this.checker = new OhterChecker();
}

public void generate() {
  ...
  for(Element element : this.elements){
    if (checker.isChecked(element)){
      ...
    }
  }
  ...
}

}

class OtherChecker {
  boolean isChecked(Element element){
    return ...
  }

}
class Report {

private Element[] elements;
private Checker checker;

public Report(Element[] elements, Checker checker){
  this.elements = elements;
  this.checker = checker;
}

public void generate() {
  ...
  for(Element element : this.elements){
    if (checker.isChecked(element)){
      ...
    }
  }
  ...
}

}

interface Checker {
  boolean isChecked(Element element);
}

class OneChecker extends Checker {
  boolean isChecked(Element element){
    return ...
  }

}

class OtherChecker extends Checker {
  boolean isChecked(Element element){
    return ...
  }

}
class Report {

private Element[] elements;
private Checker checker;

public Report(Element[] elements){
  this.elements = elements;
}

public void set(Checker checker){
  this.checker = checker;
}

public void generate() {
  ...
  for(Element element : this.elements){
    if (checker.isChecked(element)){
      ...
    }
  }
  ...
}

}

interface Checker {
  boolean isChecked(Element element);
}

class OneChecker extends Checker {
  boolean isChecked(Element element){
    return ...
  }

}

class OtherChecker extends Checker {
  boolean isChecked(Element element){
    return ...
  }

}
sinInyeccion
diagramaConInyeccionConstructor
conInyeccionSetter

Ocultación de Información

Principio Separación de Interfaces

Los clientes no deberían forzarse a depender de interfaces que no usan
— Robert Martin
Principio de Separación de Interfaces
  • Definido por Robert Martin

    • Interface Segregation Principle, ISP

      • como el cuarto de los principios SOL*I*D

Motivación Solución
  • Cuando un cliente depende de una clase que contiene una interfaz que no usa pero otros clientes sí la usan, el primer cliente será afectado por cambios que otros clientes fuercen sobre la clase que da el servicio.

  • Sería deseable evitar el acoplamiento entre clientes como sea possible y separar interfaces como sea possible. Dado que los clientes están “separados”, las interfaces deben permanecer también “separadas”.

    • En otras palabras, el interfaz de una clase puede ser rota en grupos de funciones. Cada grupo sirve a diferentes conjuntos de clientes. Así, algunos clientes usan un grupo de funciones y otros clientes usan otro grupo.

  • Este principio reconoce que hay objetos que requieren interfaces “gordas” no cohesivas (clase de utilidad, servicio, …​)

  • Sin embargo sugiere que los clientes no deberían conocerlos como una única clase. En cambio, los clientes deberían conocer clases base abstractas que tengan interfaces cohesivas.

Diseño Implementación

Violando Principio de Ocultación de la Información

secretariaSinInterfaces
class Profesor {
public void calificar(Secretaria secretaria){
  secretaria.setCalificación(10);
}
}

class Alumno {
public void matricular(Secretaria secretaria){
  secretaria.getCalificación(7);
  secretaria.setIngresosFamiliares(7, 666);
}
}

class Secretaria {
private Expediente expedientes[];

public void setCalificación(int id, int calificacion){
  ...
}
public int getCalificación(int id){
  ...
}
public int setIngresosFamiliares(int id, int cantidad){
  ...
}
public int getIngresosFamiliares(int id){
  ...
}
}
objetoSecretariaSinInterfaces

Incorporando Complejidad Arbitraria

secretariaConClases
class Profesor {
public void calificar(SecretariaProfesorado secretaria){
  secretaria.setCalificación(10);
}
}

class Alumno {
public void matricular(SecretariaAlumnado secretaria){
  secretaria.setIngresosFamiliares(7, 666);
}
}

class SecretariaProfesorado {

private Secretaria secretaria;

public SecretariaProfesorado(Secretaria secretaria){
  this.secretaria = secretaria;
}
public void setCalificacion(int id, int calificacion){
  this.secretaria.setCalificacion(id, calificacion);
}
public int getCalificacion(int id){
   this.secretaria.getCalificacion(id, calificacion);
}
}

class SecretariaAlumnado {

privareturnte Secretaria secretaria;

public SecretariaAlumnado(Secretaria secretaria){
  this.secretaria = secretaria;
}
public int setIngresosFamiliares(int id, int cantidad){
  this.secretaria.setIngresosFamiliares(id, cantidad);
}
public int getIngresosFamiliares(int id){
  return this.secretaria.getIngresosFamiliares(id, cantidad);
}
}

class Secretaria {
private Expediente expedientes[];

public void setCalificación(int id, int calificacion){
  ...
}
public int getCalificación(int id){
  ...
}
public int setIngresosFamiliares(int id, int cantidad){
  ...
}
public int getIngresosFamiliares(int id){
  ...
}
}
objetoSecretariaConClases

Solución con Interfaces, como mecanismo de Ocultación de la Información

secretariaConInterfaces
class Profesor {
public void calificar(SecretariaProfesorado secretaria){
  secretaria.setCalificación(10);
}
}

class Alumno {
public void matricular(SecretariaAlumnado secretaria){
  secretaria.setIngresosFamiliares(7, 666);
}
}

interface SecretariaProfesorado {
public void setCalificación(int id, int calificacion);
public int getCalificación(int id);
}

interface SecretariaAlumnado {
public int setIngresosFamiliares(int id, int cantidad);
public int getIngresosFamiliares(int id);
}

class Secretaria implements SecretariaProfesorado, SecretariaAlumnado {
private Expediente expedientes[];

public void setCalificación(int id, int calificacion){
  ...
}
public int getCalificación(int id){
  ...
}
public int setIngresosFamiliares(int id, int cantidad){
  ...
}
public int getIngresosFamiliares(int id){
  ...
}
}
objetoSecretariaConInterfaces
Principio de Separación de Interfaces Compromiso
  • Estas interfaces deben ser implementadas en el mismo objeto dado que la implementación de ambos interfaces manipulan los mismos datos.

    • La respuesta a esto radica en el hecho de que los clientes de un objeto no necesitan acceder a ella a través de la interfaz del objeto. Más bien, se puede acceder a él a través de una clase base del objeto o por medio de la delegación a una parte.

  • Como todos los principios SOLID se requiere una gasto de esfuerzo y tiempo adicional para aplicar durante el diseño e incrmenta la complejidad del código. Pero produce un diseño flexible.

    • Si lo aplicamos más de lo necesario resultará un código lleno de interfaces con un solo método. Asi que su aplicacion sería hecha en base a nuestra experiencia y sentido común identificando área donde la extension de código es más posible en el futuro: YAGNI!

Principio de Inversión de Dependencias

Los módulos de alto nivel no deberían depender de los módulos de bajo nivel. Ambos deberían depender de abstracciones
— Robert Martin
Principio de Inversión de Dependencias
  • Definido por Robert Martin (Dependency Inversion Principle, DIP) como uno de los principios SOLID, pudiendose entender como el resultado de aplicar rigurosamente los

    • Abierto/Cerrado de Bertrand Meyer y

    • Principios de Sustitución de Barbara Liskov

Motivación Justificación Solución Compromisos
  • Las abstracciones no deberían depender de los detalles.

  • Las clases abstractas no deberían depender de las clases concretas.

  • Cuando los módulos de alto nivel son independientes de los de bajo nivel, se pueden reutilizar los primeros con sencillez.

    • Los detalles deberían depender de las abstracciones.

    • Las clases concretas deberían depender de las clases abstractas

  • Cuando las abstracciones son independientes de los detalles, se pueden reutilizar los primeros con sencillez.

    • En este caso, las abstracciones no pueden mencionar ninguna clase derivada

  • En este caso, la instanciación de las objetos de bajo nivel dentro de la clase de alto nivel no puede ser hecha con el operador new

  • Usar este principio implica un incremento de esfuerzo, porque resultarán más clases e interfaces para mantener, código más complejo pero más flexible. Este principio no sería applicable a ciegas en cada clase o cada módulo. Si se tiene la funcionalidad de una clase que es más que possible que no cambie en el futuro, no hay necesidad de aplicar este principio: YAGNI!

Sin Principio de Inversión de Dependencias Con Principio de Inversión de Dependencias
copierSinInversion
copierConInversion
class Copier {

private KeyboardReader reader;
private PrintWriter writer;

public Copier(){
  this.reader = new KeyboardReader();
  this.writer = new PrintWriter();

public void copy(){
  ...
  char[] data = reader.read();
  ...
  writer.write(data);
  ...
}

}

class KeyboardReader{
public char[] read(){
  ...
}
}

class PrintWriter{
public  write(char[]){
  ...
}
}
class Copier {

 private Reader reader;
 private Writer writer;

 public Copier(){
   StreamFactory streamFactory = new StreamFactory();
   this.reader = streamFactory.getReader();
   this.writer = streamFactory.getWriter();
 }

 public void copy(){
   ...
   char[] data = reader.read();
   ...
   writer.write(data);
   ...
 }

}

class StreamFactory {

 public Reader getReader(){
   return ...
 }

 public Writer getWriter(){
   return ...
 }

}

interface Reader {

 char[] read();
}

class KeyboardReader implements Reader{

 public char[] read(){
   ...
 }

}

interface Writer {

 void write(char[]);
}

}

class PrintWriter implements Writer{

 public  write(char[]){
   ...
 }

}
Sin Principio de Inversión de Dependencias Con Principio de Inversión de Dependencias
sinInversion
conInversion
class Button {

 private Lamp lamp;

 public Button(Lamp lamp){
   this.lamp = lamp;
 }

 void detect(){
   boolean buttonOn = this.getPhysicalState();
   if (buttonOn) {
     this.lamp.turnOn();
   } else {
     this.lamp.turnOff();
   }
 }

 boolean getPhysicalState(){
   ....
 }

}

class Lamp {

 public void TurnOn(){
   ...
 }

 public void TurnOff(){
   ...
 }

}
interface Button {

 void detect();
}

class ButtonImplementation implments Button {

 private ButtonClient client;

 public ButtonImplementation(ButtonClient client){
   this.client = client;
 }

 void detect(){
   boolean buttonOn = this.getPhysicalState();
   if (buttonOn) {
     this.client.turnOn();
   } else {
     this.client.turnOff();
   }
}

 boolean getPhysicalState(){
   ....
 }

}

interface ButtonClient {

 void TurnOn();
 void TurnOff();
}

class Lamp extends ButtonClient {

 public void TurnOn(){
   ...
 }

 public void TurnOff(){
   ...
 }

}

Sintesis

sintesis

Bibliografía

Obra, Autor y Edición Portada Obra, Autor y Edición Portada
  • The Unified Modeling Language User Guide

    • Booch, Jacobson, Rumbaugh

    • Pearson Education, 2005

height32

  • UML Distilled. A Brief Guide to the Standard Object Modeling Language

    • Fowler, Scott

    • Addison-Wesley, 2003

height32

  • Object Oriented Analysis and Design with Applications

    • Grady Booch

    • Addison-Wesley; (2011)

height32

  • Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development

    • Larman Craig

    • Prentice Hall; (2004)

height32

  • Object-Oriented Software Construction

    • Bertrand Meyer

    • Prentice Hall; (1997)

height32

  • Eiffel : The Language (PRENTICE HALL OBJECT-ORIENTED SERIES)

    • Bertrand Meyer

    • Prentice Hall; (1991)

height32

  • Code Complete

    • Steve McConnell

    • Microsoft Press; (2004)

height32

  • Clean Code. A Handbook of Agile Software Craftsmanship

    • Robert C. Martin

    • Prentice Hall; (2008)

height32

  • An Introduction to Object-Oriented Programming

    • Timothy A. Budd

    • Addison-Wesley; (2001)

height32

  • Agile Software Development, Principles, Patterns and Practices

    • Robert-C Martin

    • Pearson; (2006)

height32

  • The Mythical Man Month. Essays on Software Engineering

    • Frederick P. Brooks

    • Prentice Hall; (1995)

height32

  • Object Solutions. Managing the Object Oriented Project

    • Grady Booch

    • Addison-Wesley; (1789)

height32

  • Refactoring. Improving the Design of Existing Code

    • Martin Fowler,Kent Beck,John Brant,William Opdyke,Don Roberts

    • Addison Wesley; (1999)

height32

  • Extreme Programming Explained. Embrace Change. Embracing Change

    • Kent Beck,Cynthia Andres

    • Addison-Wesley; (2004)

height32

  • C++ Programming Language

    • Stroustrup Bjarne

    • Addison Wesley; (2013)

height32

  • The Clean Coder: A Code of Conduct for Professional Programmers

    • Robert C. Martin

    • Addison-Wesley; (2011)

height32

  • Desarrollo ágil esencial: Vuelta a las raíces

    • Robert C. Martin

    • Anaya; (2020)

height32

Ponente

  • Luis Fernández Muñoz

setillo

  • Doctor en Inteligencia Artificial por la UPM

  • Ingeniero en Informática por la UMA

  • Diplomado en Informática por la UPM

  • Profesor Titular de ETSISI de la UPM