Devloger

SOLID - ISP - Reguła Segregacji Interfejsów

Kategorie: OOP Zasady SOLID · 0 komentarzy

SOLID - ISP - Reguła Segregacji Interfejsów

SOLID - Reguła ISP

Nadszedł czas na omówienie reguły segregacji interfejsów. Jest to już przedostatnia reguła jaka nam została do omówienia Emotikon uśmiechniętej buźki.

Do rzeczy

ISP (Interface Segregation Principle) - Reguła, która mówi, że wiele dedykowanych interfejsów jest lepszą opcją niż jeden ogólny. A klient nie powinien być zmuszony do implementacji interfejsu którego nigdy nie użyje.

Reguła ta może wydawać się skomplikowana - lecz jest naprawdę bardzo prosta w zrozumieniu Emotikon uśmiechniętej buźki.

Sens

Tworząc interfejs, nie powinniśmy nigdy dopuścić do sytuacji, w której choć jedna z klas będzie zmuszona do implementacji choć jednej metody której nie będzie wykorzystywać. Należy wówczas rozdzielić ten interfejs na kilka mniejszych.

Poszerzając

Klasa w której używamy/akceptujemy inną klasę, ma o niej wiedzę i jest od niej zależna (są to zależności tej klasy). W konsekwencji klasa ta również jest zależna od wszystkich zależności tej klasy (jeżeli takowe istnieją).

I jak możesz sobie wyobrazić - nie jest to sytuacja idealna.

Zbyt dużo wycieka nam tutaj wiedzy.

Jak może zauważyłeś, wszystkie te zasady SOLID sprowadzają się do tematu wiedzy. Wiedzy jaką jeden obiekt ma o drugim obiekcie.

Idąc dalej

Powinniśmy dostarczać jak najbardziej konkretne interfejsy (spełniające SRP). Które z kolei pozwolą nam nie polegać na całych klasach oraz na klasach które są używane przez te klasy.

Nie zagłębiać się w całą tę wiedzę. Zamiast tego jedynie polegać na konkretnej funkcjonalności której potrzebujemy (w danej klasie/metodzie) i minimum wiedzy (i zależności).

W połączeniu z regułą otwarte-zamknięte (OCP) powinniśmy zrobić taki konkretny interfejs, który pozwoli nam uniknąć sytuacji w której musielibyśmy znać typ obiektu, który został przekazany (mieć o nim wiedzę).

Zamiast tego powinniśmy pozwolić temu obiektowi zająć się całą logiką - a nam dostarczyć polimorficzny interfejs.

Zależności

Nie powinniśmy polegać na konkretnych klasach tylko na konkretnej funkcjonalności. Tym sposobem ulepszamy architekturę i zmniejszamy zależności.

W taki sposób moglibyśmy (jako klienci / klasa) używać jakiegokolwiek obiektu który implementuje konkretny (funkcjonalny) interfejs bez konieczności polegania na jakiejkolwiek konkretnej implementacji klasowej. Poleganie na funkcjonalnym interfejsie który implementuje dana klasa zamiast na całej klasie.

To klucz do zrozumienia całości.

Przykład

Złamanie reguły ISP (+OCP)

<?php

interface WorkerInterface
{
    public function work();
    public function sleep();
}

class HumanWorker implements WorkerInterface
{
    public function work()
    {
        return 'Human working.';
    }

    public function sleep()
    {
        return 'Human sleeping.';
    }
}

class AndroidWorker implements WorkerInterface
{
    public function work()
    {
        return 'Android working.';
    }

    public function sleep()
    {
        /*
            Problem ISP: Android nie potrzebuje snu, więc ta metoda
            jest niepotrzebna choć jest wymuszona przez interfejs
         */
        return null; //
    }
}

class Captain
{
    public function manage(WorkerInterface $worker)
    {
        $worker->work();
        /*
            Problem OCP: Aby uniknąć wywołania metody sleep
            na obiekcie typu AndroidWorker
            musielibyśmy złamać regułę OCP poprzez dodanie
            sprawdzenia typu (instanceof).
         */
        $worker->sleep();
    }
}

Przestrzeganie reguły ISP (+OCP)

<?php

interface ManagableInterface
{
    public function beManaged();
}

interface WorkableInterface
{
    public function work();
}

interface SleepableInterface
{
    public function sleep();
}

class HumanWorker implements WorkableInterface, SleepableInterface, ManagableInterface
{
    public function work()
    {
        return 'Human working.';
    }

    public function sleep()
    {
        return 'Human sleeping.';
    }

    public function beManaged()
    {
        $this->work();
        $this->sleep();
    }
}

class AndroidWorker implements WorkableInterface, ManagableInterface
{
    public function work()
    {
        return 'Android working.';
    }

    public function beManaged()
    {
        $this->work();
    }
}

class Captain
{
    public function manage(ManagableInterface $worker)
    {
        $worker->beManaged();
    }
}

Konsekwencje łamania zasady ISP

Nie stosowanie się do tej reguły może doprowadzić do sytuacji, w której zamiast polegać na danej funkcjonalności (jak powinniśmy) to polegamy na klasie która (potencjalnie) jest zależna od innej klasy i co za tym idzie jesteśmy zależni również od tej innej klasy...

Może się to potem przełożyć na efekt fali. Gdy jedna mała zmiana w jednej części aplikacji wpłynie na inną część aplikacji która jest od niej zależna.

Inne spojrzenie

A patrząc na to dalej z punktu wiedzy - dlaczego klient (jakaś klasa) musi mieć wiedzę o jakiejś klasie na której - jeszcze - polega inna klasa? Dlaczego? I odpowiedź jest prosta - naprawdę nie musi. Musi tylko przyjąć jakiś dowolny obiekt który dostarczy niezbędną mu do działania funkcjonalność.

I tylko tyle ta klasa musi wiedzieć, tylko na tym polegać.

Dlatego wymagać należy potrzebnej nam do działania funkcjonalności zamiast całej konkretnej klasy która ją dostarcza. Wówczas klient w ogóle nie dba (niemalże) o to co mu damy.

Dba tylko oto, czy to co mu dajemy spełnia jego oczekiwana pod względem funkcjonalnym - co jest niezbędne do poprawnego działania. Coś (obiekt) co akceptuje wymagany przez niego interfejs.

Mniejsze lepsze niż większe

Pamiętaj, że interfejsy z pojedynczymi metodami są w 100% w porządku. Czasami to nawet najlepsze wyjście. Bywa że metoda potrzebuje po prostu jednej konkretnej funkcjonalności - a nie całej klasy. Takim interfejsem jej to zapewnimy.

Dużo gorsze są duże (grube) interfejsy, w których bardzo łatwo złamać regułę SRP (pojedynczej odpowiedzialności). Powinniśmy unikać takich interfejsów. Więc jak widzisz ponownie - wszystkie zasady SOLID są ściśle powiązane Emotikon uśmiechniętej buźki.

Podsumowując

Nie róbmy metod które polegają na całych konkretnych klasach oraz ich zależnościach. Nie powinniśmy tego robić - jest bardzo złe podejście do projektowania.

I to już wszystko na temat reguły ISP Emotikon uśmiechniętej buźki.

Przed nami została już tylko jedna reguła do omówienia... Ale to w następnym artykule Emotikon uśmiechniętej buźki.

Zapraszam do komentowania oraz lektury innych moich artykułów Emotikon uśmiechniętej buźki.

Dobrego dnia.

Krystian Bogucki

Podobał Ci się ten artykuł?

Jeśli tak, to zarejestruj się aby otrzymywać powiadomienia o nowych artykułach. Nie ujawnię nikomu Twojego adresu!