Open/closed principle

Teoria

Zasada open/closed mówi o tym, że elementy systemu powinny być otwarte na rozszerzanie, ale zamknięte na modyfikacje. Oznacza to, że klasy powinny mieć możliwość rozbudowy, ale bez konieczności ingerencji w istniejący już kod.

Przykład

Rozważmy program, która wylicza i wypisuje pole powierzchni poszczególnych figur geometrycznych (w języku JAVA).

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

        List<Object> figures = new ArrayList<>();

        Square square = new Square(4);
        figures.add(square);
        
        Rectangle rectangle = new Rectangle(3,5);
        figures.add(rectangle);
        
        Triangle triangle = new Triangle(3,3);
        figures.add(triangle);
        

        AreaCalculator areaCalculator = new AreaCalculator(figures);
        areaCalculator.calculateArea();
    }
}


public class AreaCalculator {

    List<Object> figures = new ArrayList<>();
    public AreaCalculator(List<Object> figures) {
        this.figures = figures;
    }
    
    public void calculateArea(){

        for (Object figure : figures){
            if (figure.getClass() == Square.class){
                System.out.println(((Square) figure).getArea());
            } else if ( figure.getClass() == Rectangle.class) {
                System.out.println(((Rectangle) figure).getArea());
            }  else if ( figure.getClass() == Triangle.class) {
                System.out.println(((Triangle) figure).getArea());
            }
        }
    }
}

    
    
public class Square {
    private double side;

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

    public double getArea(){
        return side*side;
    }
}



public class Rectangle {

    private double sideA;
    private double sideB;

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

    public double getArea(){
        return sideA * sideB;
    }
}


public class Triangle {
    private double base;
    private double height;

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

    public double getArea(){
        return (base*height)/2;
    }
}

Podane rozwiązanie działa i daje poprawne wyniki. Zastanówmy się jednak, co by się stało, jeśli chcielibyśmy dodać nową figurę geometryczną? Zaczynamy od dodania nowej klasy, następnie należy pamiętać o dodaniu argumentu do instrukcji if w AreaCalculator, co już kłóci się z zasadą open/close, ponieważ modyfikujemy istniejący kod. Taka konstrukcja jest też trudniejsza w debuggowaniu i mniej czytelna. Spróbujmy zatem pozbyć się bolączek naszego kodu przez zastosowanie interfejsu Figure, który to będzie implementowany przez poszczególne figury.

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

        List<Object> figures = new ArrayList<>();

        Square square = new Square(4);
        figures.add(square);
        
        Rectangle rectangle = new Rectangle(3,5);
        figures.add(rectangle);
        
        Triangle triangle = new Triangle(3,3);
        figures.add(triangle);
        
        Circle circle = new Circle(5);
        figures.add(circle);
        

        AreaCalculator areaCalculator = new AreaCalculator(figures);
        areaCalculator.calculateArea();
    }
}



public class AreaCalculator {

    List<Figure> figures = new ArrayList<>();
    public AreaCalculator(List<Figure> figures) {
        this.figures = figures;
    }
    
    public void calculateArea(){

        for (Figure figure : figures){
            System.out.println(figure.getArea());
        }
    }
}


public interface Figure {
    double getArea();
}




public class Square implements Figure {
    public double side;

    public Square(double side) {
        this.side = side;
    }
    
    @Override
    public double getArea(){
        return side*side;
    }
}



public class Rectangle implements Figure {

    public double sideA;
    public double sideB;

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



public class Triangle implements Figure {
    public double base;
    public double height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }
    
    @Override
    public double getArea(){
        return (base*height)/2;
    }
}



public class Circle implements Figure{

    private double radius;

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

Po wprowadzonych zmianach, nasz program stał się łatwy w rozbudowie, a co ważne nie zachodzi już konieczność modyfikacji istniejącego kodu Zastosowanie interfejsu zmusza nas, aby klasy, które go implementują przestrzegały pewnych zasad. Chroni nas to przed błędami i ułatwia implementowanie kolejnych klas, tutaj dodatkowych figur geometrycznych. Jak widzimy czytelność również znacznie się poprawiła i program jest zdecydowanie łatwiejszy w zrozumieniu.

Dodaj komentarz