Skip to main content

Open/Close principle

Zasada otwarte-zamknięte, która mówi aby składniki oprogramowania takie jak klasy, moduły czy funkcje, które tworzymy były otwarte na rozbudowę, ale zamknięte na modyfikacje. Otwarty na rozbudowę informuje nas, że musi istnieć jakiś prosty sposób rozbudowy zachowań jakiegoś modułu. Zamknięte na modyfikacje, mówi że rozbudowa nie może być przeprowadzona w sposób, który spowoduje zmianę istniejącego kodu źródłowego. Tu mogą się przydać takie pojęcia jak polimorfizm, dziedziczenie czy też wzorzec projektowy.

Przykład złamania tej zasady:

public class Rectangle {

    private double sideA;
    private double sideB;

    public Rectangle(double sideA, double sideB) {
        this.sideA = sideA;
        this.sideB = sideB;
    }

    public double calculateArea() {
        return sideA * sideB;
    }
}
public class Triangle {

    private double sideA;
    private double height;

    public Triangle(double sideA, double height) {
        this.sideA = sideA;
        this.height = height;
    }

    public double calculateArea() {
        return 0.5 * sideA * height;
    }
}
public class Circle {

    private double radius;

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

    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}
public class AreaCalculator {

    public void calculateRectanglesArea(Rectangle[] rectangles) {
        double area = 0;
        for (Rectangle rectangle : rectangles) {
            area = rectangle.calculateArea();
            System.out.println(area);
        }
    }

    public void calculateTriangleArea(Triangle[] triangles) {
        double area = 0;
        for (Triangle triangle : triangles) {
            area = triangle.calculateArea();
            System.out.println(area);
        }
    }

    public void calculateCircleArea(Circle[] circles) {
        double area = 0;
        for (Circle circle : circles) {
            area = circle.calculateArea();
            System.out.println(area);
        }
    }
}

Mamy tu klasy reprezentujące podstawowe figury geometryczne: prostokąt, trójkąt oraz koło, a także klasę która wylicza nam pole danej figury. W klasie AreaCalculator widzimy, że mamy aż trzy metody które liczą pola w zależności od tego jaką figurę podamy. Gdybym dodała kolejną figurę np trapez to w tej klasie musiałabym napisać kolejna metodę która wyświetli mi pole tego trapezu.

Przykład zastosowania zasady:

public interface Shape {

    double calculateArea();
}

public class Triangle implements Shape{

    private double sideA;
    private double height;

    public Triangle(double sideA, double height) {
        this.sideA = sideA;
        this.height = height;
    }
    @Override
    public double calculateArea() {
        return 0.5 * sideA * height;
    }
}

public class Rectangle implements Shape{

    private double sideA;
    private double sideB;

    public Rectangle(double sideA, double sideB) {
        this.sideA = sideA;
        this.sideB = sideB;
    }
    @Override
    public double calculateArea() {
        return sideA * sideB;
    }
}

public class Circle implements Shape{
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }
    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

public class AreaCalculator {

    private double calculateArea(Shape shape) {
        double area = shape.calculateArea();
        return area;
    }

    public void showCalculateArea(Shape[] shapes){
        for (Shape shape : shapes) {
            System.out.println(calculateArea(shape));
        }
    }
}

Zmodyfikowałam swój projekt. Co się zmieniło? Dodałam interface Shape który ma tylko deklaracje metody calculateArea. Następnie każda figura geometryczna implementuje ten interface. Natomiast w klasie AreaCalculator mam dwie metody: calculateArea oraz showCalculateArea. Zwróćmy uwagę na to, do tych metod wstawiam obiekty typu Shape co oznacza ze nie musze martwic sie o wyliczanie pola dla poszczególnych rodzajów figur, bo on będzie wiedział którą metodę calculateArea ma wykonać (czy dla kola czy dla trójkąta).

Zobaczmy jak wygląda main:

public class App {
    public static void main(String[] args){

        Shape[] shapes = {new Rectangle(2,3), new Rectangle(3,4),
                        new Triangle(2,4), new Triangle(4,4),
                        new Circle(1), new Circle(2)};

        AreaCalculator areaCalculator = new AreaCalculator();
        areaCalculator.showCalculateArea(shapes);
    }
}

Jak widać nasz areaCalculator doskonale wie, które metody ma wywołać. Zagwarantowało nam to dziedziczenie. Gdybym chciała liczyć pole trapezu to wystarczy, że dodam klasę Trapez, która implementuje Interface Shape. Nie musze modyfikować klasy AreaCalculator.

 

pliki do pobrania:
SOLID Download