SOLID #2 – OCP

W kolejnej części cyklu o mnemoniku SOLID przyszła kolej na OCP – Zasada otwarte – zamknięte. Co dokładnie ona oznacza?

OCP – Open – Closed Principle

W programowaniu pewnikiem są zmiany. Zmiany są nieodłącznym aspektem podczas rozwijania projektu. Nie jesteśmy w stanie w 100% zabezpieczyć naszego kodu przed nimi aczkolwiek istnieje reguła, która nam w tym pomoże. Jest nią właśnie zasada otwarte – zamknięte. Mówi ona tyle, że nasz kod powinien być tak skonstruowany tak aby był zamknięty na edycję ale był otwarty na rozszerzenia. Na pierwszy rzut oka wydaję się, że jest tu jakaś nieścisłość. Jak mamy rozwijać i zmieniać zachowanie już istniejących modułów bez ich edycji?  Jest to możliwe a odpowiedzią na to pytanie jest programowanie obiektowe a dokładniej abstrakcja.

Spójrzmy 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 tym 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 ze względu na typ parametru. 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? Utwórzmy metodę uniwersalną  taką, 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ły by się w systemie w przyszłości. Jak tego dokonać?

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 w przyszłości przyjdzie nam dodać nową figurę, nie będziemy musieli dodawać nowej funkcji, która przyjmie jako parametr właśnie ten nowy kształt. Wystarczy, że przy tworzeniu nowej figury użyjemy dziedziczenia i przysłonimy odpowiednią metodę. Nasza klasa została zamknięta na modyfikację a w naszym systemie została dodana możliwość obliczania pól dla nowych figur bez edycji już istniejącego kodu.

Mam coś dla Ciebie

Zapisz się do mojego newslettera, a ja prześlę Ci zbiór kilkunastu praktycznych wskazówek dla programisty aplikacji mobilnych.

Menu