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!");
}
}