Este documento presenta varios principios de programación orientada a objetos como DRY (no repetirse), KISS (mantenerlo simple), principios SOLID (responsabilidad única, abierto/cerrado, sustitución de Liskov, segregación de interfaz, inversión de dependencias) y otros como la ley de Demeter y el uso de patrones de diseño y fluent APIs. El objetivo general es mejorar la modularidad, cohesión, acoplamiento bajo y mantenibilidad del código.
2. DRY
Don't Repeat Yourself
Cada intención debe declararse en un único sitio
Evitar repetir algo más de una vez
Evitar código copiar/pegar
Cada vez que se copia/pega se duplican las posibilidades
de error
Utilizar mecanismos de abstracción para capturer
elementos similares
4. Afrontar el cambio
El código debe prepararse para afrontar cambios
en los requisitos
Existen multiples motivos para cambiar
Es imposible prever los cambios futuros
...pero sí pueden valorarse los cambios más probable
Análisis de riesgos
6. Alta cohesividad
Cohesividad = Coherencia de un módulo
Cada modulo debe resolver una funcionalidad
Debe ser posible probar cada modulo por separado
7. Acoplamiento bajo
Acoplamiento = grado de interacción entre módulos
Acoplamiento bajo Mejora la modificabilidad
Despliegue independiente de cada módulo
Estabilidad frente a cambios de otros módulos
10. (S)ingle Responsability
Un módulo debe tener una única responsabilidad
Responsabilidad = Motivo para cambiar
No debe haber más de un motivo para cambiar un
módulo
En caso contrario, las responsabilidades se mezclan
Aumenta el acoplamiento
vs
11. (S)ingle responsability
Ejemplo Sistema gestión de informes
class GestorInformes {
def descargarDatos(uri: URI)
???
def prepararInforme(fichero: String): Informe =
???
def guardarInforme(db: DataBase): Unit =
???
// ...
}
Posibles cambios: forma de preparar el informe, formato de almacenamiento,...
Algunos clientes utilizan los métodos de preparación de informes
Otros clientes utilizan los métodos de almacenamiento de informes
Si se realizan cambios en la forma de almacenar los informes, los clientes
interesados solamente en preparar los informes se verían afectados
13. (O)pen/Closed
Abierto para extensión
El modulo debe adaptarse a nuevos cambios de comportamiento
Cerrado para modificar
Los cambios de comportamiento pueden realizarse sin cambiar el
código
Cambios sin recompilar, modificar código fuente original, binarios, etc.
"Open chest surgery is not needed
when putting on a coat."
14. (O)pen/Closed
Ejemplo: Filtrar productor por color
def filtraPorColor(
productos: List[Producto],
color: Color): List[Producto] = {
for ( p <- productos
; if p.color == color
) yield p
}
Problema, si hay que filtrar por altura, por anchura, etc.
¿Abierto para extension? NO, no es posible filtrar por altura
¿Cerrado para modificación? NO, si se quiere filtrar por altura, hay que tocar el código
15. (O)pen/Closed
Ejemplo:
Puede resolverse añadiendo una función de filtro
def filtra(productos: List[Producto],
criterio: Producto => Boolean): List[Producto] = {
for (p <- productos ;
if (criterio(p))) yield p
}
Filtro por color:
val filtrados = filtrador.filtra(ps, _.color == rojo)
val filtrados2 = filtrador.filtra(ps, _.altura == 12)
Filtro por altura:
16. (L)iskov Substitution
Los subtipos deben cumplir el contrato de los supertipos
Si se puede probar la propiedad q(x) para todos los x que
pertenecen a A y hay una clase B que hereda de A, entonces
todos los y de B deben cumplir q(y)
Errores habituales:
Heredar y modificar el comportamiento
Funcionalidad de los supertipos que los subtipos no siguen
17. (L)iskov Substitution
Ejemplo: Clase Pato con los métodos habituales:
haceCuak, tieneFormaPato
Si añadimos el método respira...
¿Todas las instancias cumplen?
Pato
haceCuak
tieneFormaPato
respira)
PatitoDeGomaPatoDeParque
haceCuak
tieneFormaPato
respira)
X
18. (L)iskov Substitution
Otro
ejemplo:
class Rectangulo(
var alto: Int,
var ancho:Int) {
def getAltura() = alto
def getAncho() = ancho
def setAltura(a: Int) { alto = a }
def setAncho(a: Int) { ancho = a }
def area() = alto * ancho
}
class Cuadrado(a:Int) extends Rectangulo(a,a) {
override def area() = ancho * ancho
}
Los cuadrados no cumplen el contrato de los rectángulos
19. (I)nterface Seggregation
Los clientes no deben depender de métodos que no
utilizan
Es mejor tener muchas interfaces pequeñas
Evitar módulos con mucha funcionalidad
En caso contrario aparecen dependencias no deseadas
Si un módulo depende de funcionalidad que no usa, y estas
funcionalidades cambian, puede verse afectado
ClientA
ClientB
InterfaceA
methodA1
methodA2
InterfaceB
methodB1
methodB2
Service
mehtodA1
methodA2
methodB1
methodB2
...
<<uses>>
<<uses>>
20. (D)ependency Inversion
Módulos de alto nivel no deben depender de
módulos de bajo nivel
Ambos deben depender de abstracciones
Las abstracciones no deben depender de detalles
Los detalles sí pueden depender de abstracciones
Caballero
juego()
aventura
Aventura
comienza()
MatarDragon
comienza()
CalizSagrado
comienza()
X
21. (D)ependency Inversion
Ejemplo
class Aventura {
def comienza() {
???
}
}
class MatarDragon extends Aventura
class Caballero {
var aventura: Aventura = new MatarDragon()
def juego() {
aventura.comienza()
// ...
}
}
class CalizSagrado extends Aventura
class Caballero(var aventura: Aventura) {
def juego() {
aventura.comienza()
// ...
}
}
22. (D)ependency Inversion
Disminuye el acoplamiento
Facilita las pruebas unitarias
Pueden sustituirse módulos de bajo nivel por dobles de
prueba
Inyección de dependencias
Frameworks: Spring, Guice, etc.
23. Ley de Demeter
Ley de Demeter - Principio de menor conocimiento
Cada módulo sólo se comunica con vecinos
Objetivo: disminuir acoplamiento
Disminuir número de métodos invocados
Solución de compromiso
No siempre es positivo
Puede aumentar el número de métodos en los módulos
Síntomas de mal diseño:
Usar más de un punto...
a.b.method(...)
24. Fuent APIs
Mejorar legibilidad y usabilidad de interfaces
Ventajas
Librerías que se acercan a Domain Specific Languages
Facilidades de auto-completado de los IDEs
25. Fluent APIs
Truco: Métodos de actualización devuelven el
mismo objeto
Pueden encadenarse varios métodos
Ejemplo:
Product p = new Product().
setName("Pepe").
setPrice(23);No contradice la Ley de Demeter porque actúa sobre el mismo objeto
class Product {
...
public Product setPrice(double price) {
this.price = price;
return this;
};
26. Otras recomendaciones
Patrones de diseño
Configuración externa de un módulo
Crear implementaciones por defecto
Principios GRASP
General Responsibility Assignment Software Patterns