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).