17/05/2018 | Desarrollo de software,Tecnologías

Principios S.O.L.I.D. – "Open-Closed"

Continuando con nuestro repaso de los principios SOLID, en anteriores entradas vimos el principio de responsabilidad única. Hoy nos centraremos en el segundo principio SOLID, el principio de abierto-cerrado (OCP, por sus siglas en inglés).

Este principio establece que una entidad de software (clase, módulo, función, etc) debe quedar abierta para su extensión, pero cerrada para su modificación.

Con abierta para su extensión, nos quiere decir que una entidad de software debe tener la capacidad de adaptarse a los cambios y nuevas necesidades de una aplicación, pero con la segunda parte de “cerrada para su modificación” nos da a entender que la adaptabilidad de la entidad no debe darse como resultado de la modificación del core de dicha entidad si no como resultado de un diseño que facilite la extensión sin modificaciones.

Las ventajas que nos ofrece diseñar el código aplicando este principio es un software más fácil de mantener al minimizar los cambios en la base de código de la aplicación y de ampliar funcionalidades sin modificar partes básicas de la aplicación probadas, a su vez evita generar nuevos errores en funcionalidades que ya estaban desarrolladas, probadas y funcionando correctamente.

También vemos las ventajas a la hora de implementar test unitarios en nuestro software, el aplicar este principio nos ayudará a  no tener que modificar dichos tests cada vez que se mejore y amplíe el código, mejorando la fiabilidad de nuestra base de código, y solo teniendo que crear nuevos test para las nuevas funcionalidades implementadas.

Para afianzar estos conceptos veamos a continuación un ejemplo en código sobre cómo aplicar este principio.

Tenemos una clase CalculationService que se encarga de devolvernos el cálculo del área de un polígono, el método getArea recibe como parámetro un objeto de tipo Polygon. Polygon es la clase padre de la que extienden todos los polígonos en nuestra aplicación y tiene una propiedad type que nos diferencia el tipo de polígono que es para realizar un cálculo del área apropiado, en este ejemplo utilizaremos el cuadrado y el círculo.

class CalculationService {

public void getArea(Polygon p) {
float result = 0;
if (p.type==1){
result = areaSquare(p);
} else if (p.type==2){
result = areaCircle(p);
}
return result;
}

public void areaCircle(Circle circle) {
return Math.PI * Math.pow(circle.getRadius,2);
}

public void areaSquare(Square square) {
return Math.pow(square.getSide,2);
}
}

class Polygon {
int type;
}

class Square extends Polygon {
int side;

public Square(int side) {
super.type=1;
this.side = side;
}

public int getSide(){
return this.side;
}

}

class Circle extends Polygon {
int radius;

public Circle(int radius) {
super.type=2;
this.radius = radius;
}

public int getRadius(){
return this.radius;
}

}

Si en el código anterior quisiéramos añadir un nuevo tipo de polígono, por ejemplo un triángulo, tendríamos que crear la nueva clase Triangle que extendería de Polygon, y añadir diversos cambios en la clase CalculationService para añadir un nuevo método areaTriangle y modificar getArea para registrar el nuevo tipo de polígono.

En este caso sería necesario realizar bastantes modificaciones en CalculationService para poder extender la funcionalidad, estaríamos incumpliendo el principio de abierto-cerrado.


A continuación vamos a refactorizar el código anterior para que cumpla el principio de abierto-cerrado, modificando el diseño de tal manera que permite añadir funcionalidades, en este caso concreto nuevos tipos de polígonos, sin modificar las clases y métodos existentes

class CalculationService {
public void getArea(Polygon p) {
return p.area();
}
}

class Polygon {
abstract void area();
}

class Square extends Polygon {
int side;

public Square(int side) {
this.side = side;
}

public void area() {
return Math.pow(side,2);
}
}

class Circle extends Polygon {
int radius;

public Circle(int radius) {
this.radius = radius;
}

public void area() {
return Math.PI * Math.pow(radius,2);
}
}

En este segundo ejemplo hemos creado el método area en la clase padre Polygon, los objetos que extienden de ella, Square y Circle, implementan la lógica del cálculo del área, que será diferente para cada polígono. De este modo la clase CalculationService queda mucho más limpia y no será necesario modificarla cada vez que añadamos un nuevo tipo de objeto Polygon o queramos modificar la lógica del cálculo del área en alguna implementación concreta.

Para añadir un polígono de tipo triangulo, solo sería necesario añadir la siguiente clase, de esta manera podemos extender la aplicación pero sin modificar el código existente.

class Triangle extends Polygon {
int base;
int height;

public Triangle(int base, int height) {
this.base = base;
this.height = height;
}

public void area() {
return base*height/2;
}
}

El principio de abierto-cerrado como el resto de principios nos ofrecen una guía para crear diseños de software flexible, pero también hay que tener en cuenta que hacer un diseño flexible implica tiempo y esfuerzo adicional e introduce un nuevo nivel de abstracción que aumenta la complejidad del código. Por lo tanto antes de aplicar cualquier principio es necesario conocer nuestras necesidades y dónde es interesante aplicar estos principios, como por ejemplo en áreas que es más probable que se modifiquen.

Hay muchos patrones de diseño que nos ayudan a extender el código sin cambiarlo como por ejemplo, el patrón Decorator nos ayuda a seguir el principio de abierto-cerrado. También el patrón Factory o el patrón Observer pueden usarse para diseñar una aplicación fácil de extender con mínimos cambios en el código existente.

Esperamos que estos ejemplos os hayan ayudado a entender el segundo principio de SOLID. Si queréis saber más sobre el resto de principios, seguid atentos a nuestro blog!.

Compartir en:

Relacionados