SOLID – Zasada otwarte - zamknięte
Zasada otwarte - zamknięte (Open Closes Principle – OCP) i jej konkretny opis. SOLID bez tajemnic.
Zasada otwarte — zamknięte jest drugą zasadą mnemoniku SOLID. Co dokładnie ona oznacza?
S – Zasada pojedynczej odpowiedzialności (Single Responsibility Principle – SRP)
O – Zasada otwarte – zamknięte – (Open-Closed Principle – OCP)
L – Zasada 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.
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.