Zasada, w której nagrodą za jej przestrzeganie jest łatwe wprowadzanie zmian i nowych funkcjonalności do kodu bez narażanie się na błędy, niezgodności i utratę stabilności.

Definicja

Oprogramowanie powinno być otwarte na rozszerzenia, ale zamknięte na modyfikacje.

Oznacza to, że dodając nowe funkcjonalności nie powinniśmy zmieniać istniejących klas i metod, ale dodać nowy kod. Można to osiągnąć za pomocą dziedziczenia, polimorfizmu lub kompozycji.

Przykład

Za przykład posłuży nam klasa AreaCalculator, która oblicza pole powierzchni wszystkich figur danego typu (np. protokąty) podanych przy pomocy tabeli.

public class AreaCalculator {
    public static double CalculateRectanglesAreas(Rectangle[] rectangles){
        double area = 0;
        for(Rectangle rectangle: rectangles){
            area += rectangle.getHeight() * rectangle.getWidth();
        }
        return area;
    }
    public static double CalculateCirclesAreas(Circle[] circles){
        double area = 0;
        for(Circle circle: circles){
            area += circle.getRadius() * circle.getRadius() * Math.PI;
        }
        return area;
    }
} 
public class Rectangle {
    private double height;
    private double width;

    public Rectangle(int height, int width) {
        height = height;
        width = width;
    }

    public double getHeight() {
        return height;
    }

    public double getWidth() {
        return width;
    }
} 
public class Circle {
    private double radius;

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

    public double getRadius() {
        return radius;
    }
} 

Klasa ta łamie zasadę OCP, ponieważ gdybyśmy chcieli dodać metodę obliczającą pole dla trójkątów musielibyśmy zedytować tą klasę przez co narazilibyśmy ją na wystąpienie błędów. Dodatkowo rozbudowując tą klasę sprawimy, że kod stanie się w końcu zbyt skomplikowany, trudny do testowania i łatwo będzie wywołać w nim błąd.

Aby naprawić ten błąd stworzymy interfejs o nazwie Shape do której przeniesiemy obliczanie pola powierzchni kształtu i zaimplementujemy go w klasach, które są kształtami. Zmodyfikuję też klasę AreaCalculator

public interface Shape {
    public double calculateArea();
} 
public class Rectangle implements Shape {
    private double height;
    private double width;

    public Rectangle(int height, int width) {
        this.height = height;
        this.width = width;
    }

    public double getHeight() {
        return height;
    }

    public double getWidth() {
        return width;
    }

    @Override
    public double calculateArea() {
        return height * width;
    }
} 
public class Circle implements Shape{
    private double radius;

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

    public double getRadius() {
        return radius;
    }

    @Override
    public double calculateArea() {
        return radius * radius * Math.PI;
    }
} 
public class AreaCalculator {
    public static double CalculateAreas(Shape[] shapes){
        double area = 0;
        for(Shape shape: shapes){
            area += shape.calculateArea();
        }
        return area;
    }
} 

Teraz klasa AreaCalculator jest otwarta na rozszerzenia, np. dodanie nowego kształtu. Wystarczy dodać nową klasę, która będzie implementować Shape i nadpisze metodę calculateArea. Dodatkowo nie będziemy musiali zmieniać nic w dotychczasowym kodzie, który stał się bardziej czytelny i łatwiejszy do testowania