Druga z zasad SOLID to zasada Open – Closed Principle – zasada „otwarty-zamknięty”. Mówi ona o tym, że każdy element oprogramowania powinien być otwarty na rozszerzenia ale zamknięty na modyfikacje. Oznacza to, że jeżeli chcemy rozwinąć jakąś funkcjonalność, dokonać jakichś zmian w projekcie, to powinno to nastąpić poprzez rozszerzenie istniejącego kodu, a nie jego modyfikację.
Za przykład niech posłuży nam program do obliczania sumy powierzchni różnych figur, niech to będą prostokąty. Tworzymy więc klasę Rectangle, która posiada dwa pola: width oraz height. Posiadamy także klasę AreaCalculator, która liczy sumę pól wszystkich prostokątów w tablicy.
Ractangle.java
package calculator.shapes;
public class Rectangle {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
}
AreaCalculator.java
package calculator.shapes;
public class AreaCalculator {
public double calculateArea(Rectangle[] shapes) {
double area = 0.0;
for (Rectangle rectangle : shapes) {
area += rectangle.getHeight() * rectangle.getWidth();
}
return area;
}
}
Mamy więc klasę, która oblicza sumę powierzchni prostokątów. A co jeśli chcielibyśmy rozszerzyć funkcjonalność tej klasy tak, aby obliczała również pola kół, trójkątów i innych kształtów? Za każdym razem musielibyśmy modyfikować istniejący kod wprowadzając nowe figury, po czym sprawdzać w instrukcjach warunkowych rodzaj figury i odpowiednio obliczać jej pole. Nie jest to zgodne z zasadą „otwarte/zamknięte”, która mówi że kod powinien być zamknięty na modyfikacje. Poprawnym rozwiązaniem może być zastosowanie klasy abstrakcyjnej Shape, która zawiera metodę calculateArea i po której dziedziczą klasy Rectangle, Circle, Triangle itp. Możemy wtedy wprowadzić dowolną ilość kształtów i zaimplementować sposób obliczania ich pola powierzchni bez ingerencji w samą klasę AreaCalculator, która poprawnie wyglądałaby jak poniżej.
Shape.java
package calculator.shapes;
public abstract class Shape {
public abstract double area();
}
Rectangle.java
package calculator.shapes;
public class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
@Override
public double area() {
return width * height;
}
}
Circle.java
package calculator.shapes;
public class Circle extends Shape{
public static final double PI = 3.14;
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
@Override
public double area() {
return PI * radius * radius;
}
}
AreaCalculator.java
package calculator.shapes;
public class AreaCalculator {
public double calculateArea(Shape[] shapes) {
double area = 0.0;
for (Shape shape : shapes) {
area += shape.area();
}
return area;
}
}
Main.java
import calculator.shapes.*;
public class Main {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle(15.5, 17.8);
Circle circle = new Circle(6.5);
AreaCalculator calculateAreas = new AreaCalculator();
Shape[] shapes = {rectangle, circle};
double areas = calculateAreas.calculateArea(shapes);
System.out.println("Total area: " + areas);
}
}
PODSUMOWANIE
Korzystanie z abstrakcji poprzez zastosowanie interfejsów lub klas abstrakcyjnych i polimorfizmu jest cechą wyróżniającą projekty stosujące się do zasady „otwarte-zamknięte”. Dzięki temu system staje się stabilniejszy, minimalizujemy możliwość popełnienia błędu poprzez zamknięcie klas na modyfikacje istniejącego kodu. Stosowanie tej zasady może pociągać za sobą również zastosowanie zasady jednej odpowiedzialności (SRP).