Dziś zajmiemy się omówieniem drugiej z zasad SOLID, która głosi, że oprogramowanie powinno być otwarte na rozszerzanie i zamknięte na modyfikacje. Lubię myśleć o inżynierii oprogramowania jako inżynierii budowlanej - powinno nam zależeć na tym, by aplikacja miała stabilną konstrukcję, a nie sypała się jak bierki przy każdej drobnej zmianie. 

Za przykład posłużą nam konta oszczędnościowe. Bank oferuje dwa rodzaje kont, na których odsetki naliczane są w różny sposób.

 

public enum AccountType
{
    Regular,
    Salary
};

public class SavingAccount
{
    public decimal CalculateInterests(AccountType accountType)
    {
        switch (accountType)
        {
            case AccountType.Regular:
                throw new NotImplementedException();
            case AccountType.Salary:
                throw new NotImplementedException();
            default:
                return 0;
        }
    }
}

 

Pewnego dnia otrzymujemy change request: klient decyduje się wprowadzić kolejny typ konta oszczędnościowego. Jesteśmy na tyle sprytni, że zamiast litanii instrukcji if...else wykorzystujemy switch...case i dopisujemy kolejny przypadek. Świetnie, ale co jeśli wkrótce pojawią się kolejne i kolejne typy kont? Instrukcja warunkowa będzie się szybko rozrastać, a każda zmiana będzie wymuszać zmianę dotychczasowego kodu aplikacji. Dobrym rozwiązaniem jest stworzenie interfejsu, który byłby implementowany przez klasy kont oszczędnościowych:

 

public interface ISavingAccount
{
    decimal CalculateInterests();
}

public class RegularSavingAccount : ISavingAccount
{
    public decimal CalculateInterests()
    {
        throw new NotImplementedException();
    }
}

public class SalarySavingAccount : ISavingAccount
{
    public decimal CalculateInterests()
    {
        throw new NotImplementedException();
    }
}

 

W razie potrzeby utworzenia kolejnych typów kont, wcześniej napisany kod nie musi być w żaden sposób modyfikowany, po prostu dokładamy kolejną klasę dziedziczącą po interfejsie. Cała istota Zasady Otwarte-Zamknięte: kod otwarty na rozszerzanie. Oczywiście przykład jest dużym uproszczeniem i wprowadzanie w tym przypadku abstrakcji nie jest tak zasadne jak przy większym skomplikowaniu systemu. Ułatwi nam to jednak współpracę z kodem opartym na innych wzorcach projektowych, których z pewnością użyjemy w przyszłości - np. z fabryką abstrakcyjną