SOLID – Zasada otwarte-zamknięte

Posted on by 0 comment

Zasada podstawienia Liskov jest jedną z zasad tworzenia efektywnego oprogramowania SOLID. Jeśli nie wiesz co to jest SOLIDtutaj znajdziesz artykuł, który wypełni tę lukę SOLID’nym materiałem.

Zasada podstawienia Liskov

„Funkcje które używają wskaźników lub referencji do klas bazowych, muszą być w stanie używać również obiektów klas dziedziczących po klasach bazowych, bez dokładnej znajomości tych obiektów”.

Taka jest definicja, niestety definicję mają to do siebie, że są trudnym materiałem do zrozumiałego przyswajania wiedzy. Dlatego spróbuję ją rozebrać na czynniki pierwsze, a do pełnego zrozumienia stworzę klarowny przykład zastosowania zasady podstawienia Liskov.

Śledząc uważniej powyższą definicję, możemy niewątpliwie wyszczególnić dwie struktury: klasa bazowa oraz dziedzicząca po niej klasa dziedzicząca. Pomijając zachodzące dziedziczenie, kolejnym ważnym elementem jest charakterystyka tej relacji: bez dokładnej znajomości. Znaczy ona tyle, że implementując w poprawny sposób zasadę podstawienia Liskov, nie powinniśmy się posługiwać żadnym konstrukcjami warunkowymi, aby wymusić poprawne działanie, np.:

public void funkcja(){
  if(obiekt_klasy_bazowy)
    //do sth..
  else if(obiekt_klasy_pochodnej)
    //do sth..
}

Dostarczono nam kolejną propozycję, która ułatwia naszą pracę, ale w jakim celu? No właśnie. Celem zasady podstawienia Liskov jest uniknięcie sytuacji niezamierzonego/nieprawidłowego działania stworzonego oprogramowania. Funkcje obiektów, które zostały przypisane do referencji klas bazowych (rzutowania w górę), powinny zachowywać się w taki sam sposób zarówno w przypadku rzutowania, jak i bez. Co to znaczy?

class KlasaA { ... }
class KlasaB : KlasaA { ... }

KlasaB ob1 = new KlasaB()
ob1.JakasFunkcja(); //oczekiwane dzialanie

KlasaA ob2 = KlasaB;
ob2.JakasFunkcja();

Jeżeli nie zastosujemy zasady podstawienia Liskov sposób działania funkcji JakasFunkcja() w zależności czy jest wywoływany z obiektu ob1, czy obiektu ob2 będzie różny. Takie działanie może być zaskakujące i tworzy potencjalne źródło problemów.

Przykład

Zbuduję najpierw przykład, który nie będzie wyposażony w oręże zasady podstawienia Liskov.

Załóżmy, że piszemy oprogramowanie dla dwóch urządzeń, frezarki oraz obrabiarki. Obie maszyny mają podobną specyfikę pracy, dlatego stworzymy dwie klasy Obrabiarka oraz FrezarkaObrabiarka będzie dziedziczyć po klasie Frezarka.

    class Frezarka 
    {
        public void Operacja()
        {
            Console.WriteLine("Frezarka robi coś");
        }
    }

    class Obrabiarka : Frezarka
    {
        public void Operacja()
        {
            Console.WriteLine("Obrabiarka robi coś");
        }
    }

Zajmijmy się teraz uruchomieniem stworzonych maszyn.

        Obrabiarka obr = new Obrabiarka();
        obr.Operacja(); //Wynik: "Obrabiarka robi coś"

        Frezarka frez = obr;
        frez.Operacja(); //Wynik: "Frezarka robi coś"

Jak widzimy, w obu przypadkach wywołania funkcji wykorzystujemy ten sam obiekt. Mimo to uzyskujemy różne rezultaty, jest to działanie wbrew zasady podstawienia Liskov, ponieważ zmiana referencji do obiektu nie powinna wpływać na rezultat funkcji.

Jak to naprawić? Oczywiście wykorzystując polimorfizm.

    class Frezarka 
    {
        public virtual void Operacja()
        {
            Console.WriteLine("Frezarka robi coś");
        }
    }

    class Obrabiarka : Frezarka
    {
        public override void Operacja()
        {
            Console.WriteLine("Obrabiarka robi coś");
        }
    }

Dzięki takiej implementacji, wywołanie funkcji nie zależy już od typu referencji, ale od typu obiektu. Dzięki temu uruchamiając powyższy kod, uzyskamy rezultat zgodny z omawianą zasadą SOLID.

        Obrabiarka obr = new Obrabiarka();
        obr.Operacja(); //Wynik: "Obrabiarka robi coś"

        Frezarka frez = obr;
        frez.Operacja(); //Wynik: "Obrabiarka robi coś"

Będąc solidnymi programistami, pokusimy się o refactoring. Nieodłącznym elementem polimorfizmu są struktury abstrakcyjne interfejsklasa abstrakcyjna. Tym razem wykorzystajmy interfejs i usprawnijmy kod w ten sposób.

    interface IMaszyna
    {
        void Operacja();
    }

    class Frezarka : IMaszyna
    {
        public virtual void Operacja()
        {
            Console.WriteLine("Frezarka robi coś");
        }
    }

    class Obrabiarka : Frezarka
    {
        public override void Operacja()
        {
            Console.WriteLine("Obrabiarka robi coś");
        }
    }

Podsumowanie

Kolejna zasada SOLID za nami. Zasada podstawienia Liskov pomaga uniknąć rozczarowań wynikających z odmiennego niż zamierzone działania programu. Jest nierozerwalnym elementem, gdy programujemy z użyciem interfejsów. Zapraszam do innych artykułów, w których pisze o innych składnikach składających się na SOLID.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *