SOLID – Zasada otwarte - zamknięte

Zasada otwarte - zamknięte (Open Closes Principle – OCP) i jej konkretny opis. SOLID bez tajemnic.

SOLID – Zasada otwarte - zamknięte

Zasada otwarte — zamknięte jest drugą zasadą mnemoniku SOLID. Co dokładnie ona oznacza?

SZasada pojedynczej odpowiedzialności (Single Responsibility Principle – SRP)
O – Zasada otwarte – zamknięte – (Open-Closed Principle – OCP)
LZasada podstawiania Liskov  – (Liskov Substitution Principle – LSP)
I  – Zasada segregacji interfejsu – (Interface Segregation Principle – ISP)
D – Zasada odwracania zależności – (Dependency Inversion  Principle DIP)

Zasada otwarte – zamknięte

W programowaniu pewnikiem są zmiany. Są one nieodłącznym aspektem rozwijania projektu. Nie jesteśmy w stanie w 100% zabezpieczyć naszego kodu przed nimi, aczkolwiek istnieje reguła, która może nam w tym pomóc. Tą  zasadą jest właśnie otwarte – zamknięte. Mówi ona tyle, że nasz kod powinien być tak skonstruowany w taki sposób, aby był zamknięty na edycję, ale był otwarty na rozszerzenia.

Na pierwszy rzut oka wydaję się, że jest tu jakaś nieścisłość. W jaki sposób mamy rozwijać i zmieniać zachowanie już istniejących klas bez ich aktualizacji?

Jest to możliwe, a odpowiedzią na to pytanie jest programowanie obiektowe a dokładniej abstrakcja. Spójrz poniżej:

public static class AreaCalculator
{
    public static double CalculateRectanglesAreas (Rectangle[] rectangles)
    {
        double area = 0;
        foreach (var rectangle in rectangles)
            area += rectangle.Height * rectangle.Width;
        return area;
    }
    public static double CalculateTrianglesAreas (Triangle[] triangles)
    {
        double area = 0;
        foreach (var triangle in triangles)
            area += (triangle.Base * triangle.Height) / 2;
        return area;
    }
}

Mamy tu dwie metody przeznaczone do obliczania sumy pól figur przekazanych jako parametr. Oddzielna istnieje dla trójkątów i oddzielna dla prostokątów. Kiedy zechcemy dodać kolejną figurę, będziemy musieli zmodyfikować klasę i dodać kolejną metodę. Łamiemy przy okazji inną ważną zasadę wytwarzania oprogramowania o nazwie DRY (Don’t Repeat Yourself – Nie powtarzaj się), ponieważ metody te robią w zasadzie to samo, a są rozbite tylko ze względu na typ parametru.

Czysty kod. Podręcznik dobrego programisty
W księgarni informatycznej Helion znajdziesz: Czysty kod. Podręcznik dobrego programisty , autor: Robert C. Martin, wydawnictwo: Helion. Produkt dostepny w formacie: Książka, ebook. Pobierz i przeczytaj darmowy fragment.
Czysty kod. Podręcznik dobrego programisty

Refaktoryzacja

Czy nie byłoby wspaniale mieć tylko jedną funkcję do obliczania sumy pól danej figury i być zamkniętym na dodawanie kolejnej metody po dodaniu nowej figury do systemu? Jasne, że tak.

Utwórzmy zatem uniwersalną metodę, która jako swój parametr przyjmie abstrakcyjny byt o nazwie Shape.  Wtedy jedną funkcją moglibyśmy załatwić problem dodania obliczania pól dla nowych figur, które pojawiłyby się w przyszłości. Jak tego dokonać? Wykorzystajmy dobrodziejstwo obiektowości.

Zacznijmy od utworzenia abstrakcyjnej klasy bazowej o nazwie Shape.

public abstract class Shape
{
    public abstract double CalculateArea ();
}

Warto zaznaczyć, że równoważnym rozwiązaniem byłoby utworzenie interfejsu.
Następnie utwórzmy klasę Rectangle.

public class Rectangle : Shape
{
    public double Height { get; set; }
    public double Width { get; set; }
    
    public override double CalculateArea ()
    {
        return Width * Height;
    }
}

Dodałem klasę AreaCalculator, a do jej wnętrza przeniosłem zmodyfikowaną metodę CalculateArea().

public static class AreaCalculator
{
    public double CalculateArea (Shape[] shapes)
    {
        double area = 0;

        foreach (var shape in shapes)
            area += shape.CalculateArea ();
        return area;
    }
}

Teraz gdy przekażemy do tej metody jako parametr tablicę obiektów Rectangle czy Triangle metoda będzie zwracać poprawne wyniki w zależności od rodzaju przekazanej figury. A to wszystko dzięki polimorfizmowi. Teraz gdy przyjdzie nam dodać nową figurę, nie będziemy musieli dodawać nowej metody, która będzie przyjmować ten nowy kształt. Wystarczy, że przy tworzeniu nowej figury użyjemy dziedziczenia i przysłonimy odpowiednią metodę.

Zamknęliśmy naszą klasę na modyfikacje, a dodatkowo dodaliśmy możliwość obliczania pól dla nowych figur bez edycji już istniejącego kodu.