S.O.L.I.D.

SOLID – to zbiór reguł zaproponowanych przez Roberta C. Martina, mających na celu poprawienie jakości kodu.

S – Single responsible principle – zasada pojedynczej odpowiedzialności

Klasa/moduł/metoda powinna robić jedną rzecz (nie rozmieniać się na drobne).

O – Open-Closed principle – zasada otwarty-zamknięty

Klasa powinna być otwarta na rozszerzenia, ale zamknięta na modyfikacje. Przykład klasy niespełniającej tej zasady:

public class Zoo{
   public static void dajcieGlos(List<Zwierze> zwierzeta) {
      for (Zwierze z : zwierzeta) {
         if (z instanceof Kot) {
            ((Kot)z).miaucz();
         } else if (z instanceof Ptak) {
            ((Ptak)z).ladnieSpiewaj();
         }
      }
   }
}

Lepszym rozwiązaniem jest wprowadzenie interfejsu, który to będą implementowały wszystkie zwierzęta a klasa Zoo będzie wykorzystywała metodę tego interfejsu (dzięki temu możemy rozszerzać Zoo o nowe zwierzęta nie modyfikując klasy Zoo):

interface DajaceGlosZwierze {
    void dajGlos();
}

public class Zoo{
   public static void dajcieGlos(List<DajaceGlosZwierze> zwierzeta) {
      for (DajaceGlosZwierze z : zwierzeta) {
         z.dajGlos();
      }
   }
}

L – Liskov substitution principle – zasada podstawienia Liskova

Jeśli klasa Dziecko rozszerza klasę Rodzic, to klasę Dziecko możemy używać w ten sam sposób co klasę Rodzic – bez niespodziewanych (wręcz dziwnych) zachowań:

class Rodzic {
    public String dodajSufiks(String a, String sufiks){
        return a + "rodzic" + sufiks;
    }
}

class DzieckoLamiaceZasadeLiskov extends Rodzic {
    public String dodajSufiks(String a, String sufiks){
        return "";
    }
}

class DzieckoZachowujaceZasadeLiskov extends Rodzic {
    public String dodajSufiks(String a, String sufiks){
        return a + "dziecko" + sufiks;
    }
} 

Bardziej „żywy” przykład:

class KontoBankowe {

    protected double stanKonta;

    public void wplac(double ilosc){
        //obsluga wplaty na konto
    }

    public double wyplac(double ilosc){
        //obsluga wyplaty z konta
    }

}

class OszczednoscioweKontoBankoweLamiaceZasadeLiskov extends KontoBankowe {

    public void wplac(double ilosc){
        this.stanKonta = 0.0;
    }

    public double wyplac(double ilosc){
        return 0.0;
    }

}

class OszczednoscioweKontoBankoweZachowujaceZasadeLiskov extends KontoBankowe {

    public void wplac(double ilosc){
        this.stanKonta + ilosc * 0.02;
    }

    public double wyplac(double ilosc){
        this.stanKonta -= ilosc;
        return ilosc;
    }

}

I – Interface Segregation Principle – zasada segregacji interfejsów

Chodzi tutaj o poprawny podział interfejsów – tak, aby żadna z klas implementujących któryś z nich nie musiała się „mocno nagimnastykować”. Przykład problematycznego interfejsu:

interface Zamowienie {
    Pizza zamowPizze();
    Pierogi zamowPierogi();
    Burger zamowBurgera();
    Cola zamowCole();
    Pepsi zamowPepsi();
}

class PizzeriaWloaska implements Zamowienie {

    public Pizza zamowPizze(){
        //obsluga zamawiania pizzy
    }

    public Pierogi zamowPierogi() {
        throw new UnsupportedOperationException();
    }

    public Burger zamowBurgera() {
        throw new UnsupportedOperationException();
    }

    public Cola zamowCole() {
        throw new UnsupportedOperationException();
    }

    public Pepsi zamowPepsi() {
        throw new UnsupportedOperationException();
    }
}

Lepszym rozwiązaniem byłoby rozdzielenie tego interfejsu:

interface ZamowieniePizzy {
    Pizza zamowPizze();
}

interface ZamowieniePierogow {
    Pizza zamowPizze();
}

//...

class PizzeriaWloaska implements ZamowieniePizzy {

    public Pizza zamowPizze(){
        //obsluga zamawiania pizzy
    }

}

D – Dependency Inversion principle – zasada odwrócenia zależności

Mówi ona o tym, że powinniśmy polegać na abstrakcjach wysokiego poziomu, a nie na konkretnych implementacjach. To może przykład problematycznego kodu:

/*
KLASA WYSOKIEGO POZIOMU
*/
class UslugaPowiadomien {
    private EmailUsluga emailUsluga;

    public UslugaPowiadomien() {
        this.emailUsluga = new EmailUsluga();
    }

    public void wyslijPowiadomienie(String wiadomość) {
        emailUsluga.wyslijEmail(wiadomość);
    }
}

/*
KLASA NISKIEGO POZIOMU - KONKRETNA IMPLEMENTACJA
*/
class EmailUsluga {
    public void wyslijEmail(String wiadomość) {
        System.out.println("Wysyłanie emaila: " + wiadomość);
    }
}

public class Main {
    public static void main(String[] args) {
        UslugaPowiadomien usługa = new UslugaPowiadomien();
        usługa.wyslijPowiadomienie("Cześć Świecie!");
    }
}

Aby dostosować kod do zasady odwrócenia zależności, należy stworzyć w module wyższego poziomu interfejs, który to będzie implementowany w klasach niskiego poziomu. Wtedy klasa wysokiego poziomu polega na tym interfejsie a nie na konkretnej implementacji niskiego poziomu:

/*
INTERFEJS WYSOKIEGO POZIOMU
*/
interface UslugaWiadomosci {
    void wyslijWiadomosc(String wiadomosc);
}

/*
KLASA WYSOKIEGO POZIOMU - KORZYSTAJACA Z INTERJEJSU WYSOKIEGO POZIOMU
*/
class UslugaPowiadomien {
    private UslugaWiadomosci uslugaWiadomosci;

    public UslugaPowiadomien(UslugaWiadomosci uslugaWiadomosci) {
        this.uslugaWiadomosci = uslugaWiadomosci;
    }

    public void wyslijPowiadomienie(String wiadomosc) {
        uslugaWiadomosci.wyslijWiadomosc(wiadomosc);
    }
}
 
/*
KLASY NISKIEGO POZIOMU - KONKRETNE IMPLEMENTACJE
*/
class EmailUsluga implements UslugaWiadomosci {
    public void wyslijWiadomosc(String wiadomosc) {
        System.out.println("Wysyłanie emaila: " + wiadomosc);
    }
}

class SmsUsluga implements UslugaWiadomosci {
    public void wyslijWiadomosc(String wiadomosc) {
        System.out.println("Wysyłanie SMSa: " + wiadomosc);
    }
}

/*
KLIENT DECYDUJE Z KTORA KLASA NISKIEGO POZIOMU BEDZIE POWIAZANA KLASA WYSOKIEGO POZIOMU
*/

public class Main {
    public static void main(String[] args) {
        UslugaWiadomosci emailUsluga = new EmailUsluga();
        UslugaWiadomosci smsUsluga = new SmsUsluga();

        UslugaPowiadomien usluga = new UslugaPowiadomien(emailUsluga);
        usluga.wyslijPowiadomienie("Cześć Świecie!");

        usluga = new UslugaPowiadomien(smsUsluga);
        usluga.wyslijPowiadomienie("Cześć Świecie!");
    }
}

Warto zajrzeć

  1. https://blog.cleancoder.com/uncle-bob/2020/10/18/Solid-Relevance.html