DOJO-CHO

córdoba edition

19.04.2018

SOLID PRINCIPLES

S

O

L

I

D

Single Responsability Principle

Open/Closed Principle

Liskov Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

¿pero para qué?

code rot /kōd rät

Cuando una aplicación se vuelve una masa purulenta de código que los desarrolladores encuentran cada vez más difícil de mantener.

(código podrido)

¿Cuáles son los síntomas?

RIGIDEZ

Un cambio menor implica una reconstrucción completa del sistema.

FRAGILIDAD

Unos cambios en un módulo afectan el comportamiento de otros módulos no relacionados.

INMOVILIDAD

Los componentes internos de un módulo no pueden extraerse y reutilizarse en nuevos entornos.

VISCOSIDAD

Compilar y testear es difícil y tarda mucho tiempo. Un cambio simple es costoso e implica modificar múltiples lugares o capas.

S

O

L

I

D

S

Una clase debería tener sólo una razón para mutar.

Toda clase o estructura similar en el código tiene que hacer una sola cosa.

Lo que haga la clase debe asociarse a un mismo propósito.

Esto significa que la clase debe tener cohesión. No quiere decir que tenga que tener sólo un método o propiedad.

Cuando el propósito de la clase se cumple, pueden mutar uno o varios miembros.

El cambio puede provocar que muten otras clases también.

S

O

L

I

D

O

Una clase debería estar abierta para ser extendida.

La clase debe diseñarse permitiendo que se agregue funcionalidad a medida que surjan nuevos requerimientos.

Una clase debería estar cerrada para ser modificada.

Una vez que una clase se ha desarrollado, no debe ser modificada, excepto para corregir errores.

¿No es esto contradictorio?

O

Un buen diseño de clase permite añadir funcionalidad sin editar código preexistente.

Esto se logra utilizando abstracciones o interfaces en vez de clases concretas. La funcionalidad se agrega creando clases que implementen estas interfaces.

Evitar la edición de código preexistente reduce el riesgo de crear nuevos errores.

S

O

L

I

D

L

Cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias entre ellas.

Todas las subclases deben operar dentro del comportamiento esperado de sus clases base, por más que la funcionalidad específica sea diferente.

Para que una clase sea un subtipo en términos de comportamiento, debe heredar sus miembros y operar dentro de los límites de su comportamiento original.

Si un subtipo hace algo que la clase base no espera, está en violación de este principio.

L

public class Rectangle {
  private double height;
  private double width;

  public double area();

  public void setHeight(double height);
  public void setWidth(double width);
}

L

public class Square extends Rectangle {  
  public void setHeight(double height) {
    super.setHeight(height);
    super.setWidth(height);
  }

  public void setWidth(double width) {
    setHeight(width);
  }
}

Esta implementación viola el principio de sustitución de Liskov. ¿Por qué?

L

package com.oop;  
import java.util.*;

class Stack<T> extends ArrayList<T> {  
  private int stackPointer = 0;

  public void push(T item) {
    add(stackPointer++, item);
  }

  public T pop() {
    return remove(--stackPointer);
  }

  public static void main(String[] args) {
    Stack<String> myStack = new Stack<String>();

    myStack.push('1');
    myStack.clear();
    String s = myStack.pop();
  }
}

Esta implementación viola el principio de sustitución de Liskov. ¿Por qué?

L

package com.oop;  
import java.util.*;	

class Stack<T> {  
  ArrayList<T> contents = new ArrayList<T>();
  private int stackPointer = 0;

  public void push(T item) {
    contents.add(stackPointer++, item);
  }

  public T pop() {
    return contents.remove(--stackPointer);
  }

  public static void main(String[] args) {
    Stack<String> myStack = new Stack<String>();
    myStack.push('1');
    // this method is gone now :)
    // myStack.clear();
    String s = myStack.pop();
  }
}

Esta implementación sigue el principio de sustitución de Liskov. ¿Por qué?

S

O

L

I

D

I

Un cliente no debe ser obligado a depender de miembros de interfaz que no usa.

Este principio propone descomponer interfaces no cohesivas en múltiples interfaces más pequeñas y cohesivas.

Mientras más pequeña sea una interfaz, más fácil es su implementación y reutilización.

La comunicación entre clase y cliente se hace a través de interfaces bien definidas, minimizando la dependencia respecto de miembros en desuso y reduciendo el acoplamiento.

I

Mientras menos clases compartan interfaces, menos cambios surgen a partir del cambio en una interfaz.

Esto incrementa la robustez del código.

"No dependas de aquello que no necesitas"

I

Esta interfaz viola el principio de segregación. ¿Por qué?

public interface Messenger {
  askForCard();
  tellInvalidCard();
  askForPin();
  tellInvalidPin();
  tellCardWasSeized();
  askForAccount();
  tellNotEnoughMoneyInAccount();
  tellAmountDeposited();
  tellBalance();
}

I

Esta interfaz sigue el principio de segregación. ¿Por qué?

public interface LoginMessenger {
  askForCard();
  tellInvalidCard();
  askForPin();
  tellInvalidPin();	
}
public interface WithdrawalMessenger {
  tellNotEnoughMoneyInAccount();
  askForFeeConfirmation();
}

S

O

L

I

D

D

Un módulo de alto nivel no debe depender de un módulo de bajo nivel.

Debe depender de abstracciones. 

Una abstracción no debe depender de particularidades; las particularidades deben depender de la abstracción.

Si las particularidades de la abstracción cambian, el módulo de alto nivel no debe verse afectado.

Esto reduce el acoplamiento y impacto de cambios a nivel diseño. También permite probar componentes de manera aislada.

D

¿Soldarías una lámpara directamente al cableado eléctrico de una pared?

Pensando en el principio de inversión de dependencias...

¿Preguntas?