Zasady SOLID

SOLID – Zasada otwarte-zamknięte

Zasada otwarte-zamknięte czyli open/closed principle (OCP) została sformułowana przez Bertranda Meyera.

Wikipedia mówi, że:

Przy zmianie wymagań nie powinien być zmieniany stary działający kod, ale dodawany nowy, który rozszerza zachowania.

zaś na innej stronie Wikipedii czytamy, że:

jedna z zasad programowania mówiąca, że elementy systemu takie, jak klasy, moduły, funkcje itd. powinny być otwarte na rozszerzenie, ale zamknięte na modyfikacje. Oznacza to, iż można zmienić zachowanie takiego elementu bez zmiany jego kodu. Jest to szczególnie ważne w środowisku produkcyjnym, gdzie zmiany kodu źródłowego mogą być niewskazane i powodować ryzyko wprowadzenia błędu. Program, który trzyma się tej zasady, nie wymaga zmian w kodzie, więc nie jest narażony na powyższe ryzyko.

Przykładem, który mnie absolutnie przekonuje do tej zasady, a przy tym w całości rozjaśnia jej istotę to sumowanie powierzchni różnych figur geometrycznych. Przykład zaczerpnąłem ze strony Joela Abrahamssona lub ExceptionNotFound chociaż nie wiem gdzie było pierwsze. Może jeszcze gdzieś indziej :).

A jak to jest ze stosowaniem tej wytycznej w codziennym życiu programisty?
Tworzenie alei ifów lub też case’ów w switch’u to częsta praktyka mniej doświadczonych programistów. Sam też tak robiłem i wszystko działało jak malowane. Klient był zadowolony, ale ja po pewnym czasie już nie.

Przykład

Mamy klasę reprezentującą kwadrat:

public class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }
}

A metoda sumując powierzchnie wszystkich prostokątów wygląda tak:

public class AreaCalculator
{
    public double Area(Rectangle[] shapes)
    {
        double area = 0;
        foreach (var shape in shapes)
        {
            area += shape.Width*shape.Height;
        }
    return area;
}
}

Co jeżeli jednak będziemy chcieli włączyć do zliczania także koła? Będziemy musieli stworzyć klasę reprezentującą koło:

public class Circle
{
    public double Radius { get; set; }
}

a następnie obsłużyć obliczanie powierzchni tej figury w kodzie (tutaj jawnie naruszamy zasadę otwarte-zamknięte)

public double Area(object[] shapes)
{
    double area = 0;
    foreach (var shape in shapes)
    {
        if (shape is Rectangle)
        {
            Rectangle rectangle = (Rectangle) shape;
            area += rectangle.Width*rectangle.Height;
        }
        else
        {
            Circle circle = (Circle)shape;
            area += circle.Radius * circle.Radius * Math.PI;
        }
    }
    return area;
}

Dodanie kolejne figury będzie skutkować dodaniem kolejnego ifa i tak można w nieskończoność. Można też zastosować zasadę otwarte-zamknięte oraz skorzystać z polimorfizmu oferowanego przez C#. Czyli tworzymy klasę bazową reprezentującą wszystkie kształty:

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

Dla poszczególnych figur tworzymy reprezentacje w postaci klas konkretnych posiadających swoje implementacje metody Area dziedziczonej z klasy Shape:

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

public class Circle : Shape
{
    public double Radius { get; set; }
    public override double Area()
    {
        return Radius*Radius*Math.PI;
    }
}

A dalej nie interesuje nas co tam w środku tego kształtu siedzi. Ważne, że taki obiekt zwraca swoją powierzchnię.

public double Area(Shape[] shapes)
{
    double area = 0;
    foreach (var shape in shapes)
    {
        area += shape.Area();
    }
    return area;
}

Reszta jest milczeniem, a dodanie nowego kształtu to dodanie nowej klasy dziedziczącej po klasie Shape. Modyfikacja metody zliczającej sumę powierzchni wszystkich figur jest zbyteczna. Cymes.

Antywzorzec

Dzięki eksploracji tej zasady zrozumiałem także dlaczego enumy wcale nie są takie dobre jak mi się wcześniej wydawało. Byłem od nich uzależniony i teraz tego żałuję, ale lepiej późno niż wcale. Dlaczego? Zaczynało się zwykle od dwóch, trzech, czterech pozycji, które były obsługiwane za pomocą ifów lub case’ów w switch’ach, a potem enum był rozszerzany, a za nim cała reszta. Tasiemce rosły, patrzyło się na to z obrzydzeniem. Zasada otwarte-zamknięte czyni kod pięknym i dlatego zachęcam do pisania kodu zgodnie z tą wytyczną.

Co zyskujemy?

  1. Klarowny, czysty kod.
  2. Mniej „czerwonego światła” podczas uruchamiania testów.
  3. Zwiększamy modularyzację naszego systemu,  także o czym pisze Arek Benedykt otwieramy możliwość na dynamiczne ładowanie specyficznych dla klienta reguł funkcjonowania systemu np:. sposoby obliczania prowizji, zniżek, etc.
  4. Zwiększamy łatwość testowania naszej aplikacji.

Użyteczne, ale nieoczywiste linki, do których zaglądam jak mam wątpliwości:
http://www.benedykt.net/tag/solid/
http://www.pzielinski.com/?p=422

Leave a Reply

Your email address will not be published. Required fields are marked *