Išsamus vadovas apie SOLID objektinio programavimo principus, paaiškinantis kiekvieną principą su pavyzdžiais ir praktiniais patarimais kuriant palaikomą ir plečiamą programinę įrangą.
SOLID principai: objektinio programavimo gairės patikimai programinei įrangai
Programinės įrangos kūrimo pasaulyje svarbiausia yra kurti patikimas, palaikomas ir plečiamas programas. Objektinis programavimas (OP) siūlo galingą paradigmą šiems tikslams pasiekti, tačiau labai svarbu laikytis nusistovėjusių principų, kad nebūtų kuriamos sudėtingos ir trapios sistemos. SOLID principai, penkių pagrindinių gairių rinkinys, pateikia planą, kaip projektuoti programinę įrangą, kurią būtų lengva suprasti, testuoti ir modifikuoti. Šis išsamus vadovas išsamiai nagrinėja kiekvieną principą, pateikdamas praktinių pavyzdžių ir įžvalgų, padėsiančių jums kurti geresnę programinę įrangą.
Kas yra SOLID principai?
SOLID principus pristatė Robertas C. Martinas (taip pat žinomas kaip „Dėdė Bobas“) ir jie yra objektinio programavimo kertinis akmuo. Tai nėra griežtos taisyklės, o gairės, padedančios programuotojams kurti lengviau palaikomą ir lankstesnį kodą. Akronimas SOLID reiškia:
- S - Vienos atsakomybės principas (Single Responsibility Principle)
- O - Atvirumo/uždarymo principas (Open/Closed Principle)
- L - Liskov pakeitimo principas (Liskov Substitution Principle)
- I - Sąsajų atskyrimo principas (Interface Segregation Principle)
- D - Priklausomybių inversijos principas (Dependency Inversion Principle)
Pasigilinkime į kiekvieną principą ir ištirkime, kaip jie prisideda prie geresnio programinės įrangos projektavimo.
1. Vienos atsakomybės principas (SRP)
Apibrėžimas
Vienos atsakomybės principas teigia, kad klasė turėtų turėti tik vieną priežastį keistis. Kitaip tariant, klasė turėtų atlikti tik vieną darbą ar atsakomybę. Jei klasė turi kelias atsakomybes, ji tampa glaudžiai susieta ir sunkiai palaikoma. Bet koks vienos atsakomybės pakeitimas gali netyčia paveikti kitas klasės dalis, sukeldamas netikėtų klaidų ir padidindamas sudėtingumą.
Paaiškinimas ir nauda
Pagrindinė SRP laikymosi nauda yra padidėjęs moduliškumas ir palaikomumas. Kai klasė turi vieną atsakomybę, ją lengviau suprasti, testuoti ir modifikuoti. Pakeitimai rečiau sukelia nenumatytų pasekmių, o klasę galima pakartotinai naudoti kitose programos dalyse neįvedant nereikalingų priklausomybių. Tai taip pat skatina geresnę kodo organizaciją, nes klasės yra orientuotos į konkrečias užduotis.
Pavyzdys
Apsvarstykite klasę pavadinimu `User`, kuri tvarko tiek vartotojo autentifikavimą, tiek vartotojo profilio valdymą. Ši klasė pažeidžia SRP, nes ji turi dvi skirtingas atsakomybes.
SRP pažeidimas (pavyzdys)
```java public class User { public void authenticate(String username, String password) { // Authentication logic } public void changePassword(String oldPassword, String newPassword) { // Password change logic } public void updateProfile(String name, String email) { // Profile update logic } } ```Norėdami laikytis SRP, galime atskirti šias atsakomybes į skirtingas klases:
Laikantis SRP (pavyzdys)Šiame patikslintame dizaine `UserAuthenticator` tvarko vartotojo autentifikavimą, o `UserProfileManager` – vartotojo profilio valdymą. Kiekviena klasė turi vieną atsakomybę, todėl kodas tampa moduliškesnis ir lengviau palaikomas.
Praktiniai patarimai
- Nustatykite skirtingas klasės atsakomybes.
- Atskirkite šias atsakomybes į skirtingas klases.
- Užtikrinkite, kad kiekviena klasė turėtų aiškų ir gerai apibrėžtą tikslą.
2. Atvirumo/uždarymo principas (OCP)
Apibrėžimas
Atvirumo/uždarymo principas teigia, kad programinės įrangos vienetai (klasės, moduliai, funkcijos ir t. t.) turėtų būti atviri plėtrai, bet uždari modifikavimui. Tai reiškia, kad turėtumėte galėti pridėti naują funkcionalumą į sistemą nekeisdami esamo kodo.
Paaiškinimas ir nauda
OCP yra labai svarbus kuriant palaikomą ir plečiamą programinę įrangą. Kai reikia pridėti naujų funkcijų ar elgsenų, neturėtumėte keisti esamo kodo, kuris jau veikia teisingai. Esamo kodo keitimas padidina riziką įvesti klaidų ir sugadinti esamą funkcionalumą. Laikydamiesi OCP, galite išplėsti sistemos funkcionalumą nepaveikdami jos stabilumo.
Pavyzdys
Apsvarstykite klasę pavadinimu `AreaCalculator`, kuri skaičiuoja skirtingų figūrų plotą. Iš pradžių ji gali palaikyti tik stačiakampių ploto skaičiavimą.
OCP pažeidimas (pavyzdys)Jei norime pridėti apskritimų ploto skaičiavimo palaikymą, turime modifikuoti `AreaCalculator` klasę, pažeisdami OCP.
Norėdami laikytis OCP, galime naudoti sąsają arba abstrakčią klasę, kad apibrėžtume bendrą `area()` metodą visoms figūroms.
Laikantis OCP (pavyzdys)
```java interface Shape { double area(); } class Rectangle implements Shape { double width; double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public double area() { return width * height; } } class Circle implements Shape { double radius; public Circle(double radius) { this.radius = radius; } @Override public double area() { return Math.PI * radius * radius; } } public class AreaCalculator { public double calculateArea(Shape shape) { return shape.area(); } } ```Dabar, norėdami pridėti palaikymą naujai figūrai, mums tiesiog reikia sukurti naują klasę, kuri įgyvendina `Shape` sąsają, nekeičiant `AreaCalculator` klasės.
Praktiniai patarimai
- Naudokite sąsajas arba abstrakčias klases bendroms elgsenoms apibrėžti.
- Projektuokite savo kodą taip, kad jį būtų galima plėsti paveldėjimu arba kompozicija.
- Venkite keisti esamą kodą pridedant naują funkcionalumą.
3. Liskov pakeitimo principas (LSP)
Apibrėžimas
Liskov pakeitimo principas teigia, kad potipiai turi būti pakeičiami savo baziniais tipais, nepakeičiant programos teisingumo. Paprasčiau tariant, jei turite bazinę klasę ir išvestinę klasę, turėtumėte galėti naudoti išvestinę klasę visur, kur naudojate bazinę klasę, nesukeldami netikėtos elgsenos.
Paaiškinimas ir nauda
LSP užtikrina, kad paveldėjimas būtų naudojamas teisingai ir kad išvestinės klasės elgtųsi nuosekliai su savo bazinėmis klasėmis. LSP pažeidimas gali sukelti netikėtų klaidų ir apsunkinti sistemos elgsenos supratimą. Laikantis LSP, skatinamas kodo pakartotinis naudojimas ir palaikomumas.
Pavyzdys
Apsvarstykite bazinę klasę pavadinimu `Bird` su metodu `fly()`. Išvestinė klasė pavadinimu `Penguin` paveldi iš `Bird`. Tačiau pingvinai negali skristi.
LSP pažeidimas (pavyzdys)Šiame pavyzdyje `Penguin` klasė pažeidžia LSP, nes ji perrašo `fly()` metodą ir išmeta išimtį. Jei bandysite naudoti `Penguin` objektą ten, kur tikimasi `Bird` objekto, gausite netikėtą išimtį.
Norėdami laikytis LSP, galime įvesti naują sąsają arba abstrakčią klasę, kuri atstovauja skraidantiems paukščiams.
Laikantis LSP (pavyzdys)Dabar tik tos klasės, kurios gali skristi, įgyvendina `FlyingBird` sąsają. `Penguin` klasė daugiau nepažeidžia LSP.
Praktiniai patarimai
- Užtikrinkite, kad išvestinės klasės elgtųsi nuosekliai su savo bazinėmis klasėmis.
- Venkite išmesti išimtis perrašytuose metoduose, jei bazinė klasė jų neišmeta.
- Jei išvestinė klasė negali įgyvendinti metodo iš bazinės klasės, apsvarstykite galimybę naudoti kitokį dizainą.
4. Sąsajų atskyrimo principas (ISP)
Apibrėžimas
Sąsajų atskyrimo principas teigia, kad klientai neturėtų būti verčiami priklausyti nuo metodų, kurių jie nenaudoja. Kitaip tariant, sąsaja turėtų būti pritaikyta specifiniams savo klientų poreikiams. Didelės, monolitinės sąsajos turėtų būti suskaidytos į mažesnes, labiau orientuotas sąsajas.
Paaiškinimas ir nauda
ISP neleidžia klientams būti verčiamiems įgyvendinti metodų, kurių jiems nereikia, taip sumažinant susiejimą ir pagerinant kodo palaikomumą. Kai sąsaja yra per didelė, klientai tampa priklausomi nuo metodų, kurie yra nesvarbūs jų specifiniams poreikiams. Tai gali sukelti nereikalingą sudėtingumą ir padidinti klaidų įvedimo riziką. Laikydamiesi ISP, galite sukurti labiau orientuotas ir pakartotinai naudojamas sąsajas.
Pavyzdys
Apsvarstykite didelę sąsają pavadinimu `Machine`, kuri apibrėžia spausdinimo, skenavimo ir faksavimo metodus.
ISP pažeidimas (pavyzdys)
```java interface Machine { void print(); void scan(); void fax(); } class SimplePrinter implements Machine { @Override public void print() { // Printing logic } @Override public void scan() { // This printer cannot scan, so we throw an exception or leave it empty throw new UnsupportedOperationException(); } @Override public void fax() { // This printer cannot fax, so we throw an exception or leave it empty throw new UnsupportedOperationException(); } } ````SimplePrinter` klasei reikia įgyvendinti tik `print()` metodą, tačiau ji yra priversta įgyvendinti ir `scan()` bei `fax()` metodus, taip pažeidžiant ISP.
Norėdami laikytis ISP, galime suskaidyti `Machine` sąsają į mažesnes sąsajas:
Laikantis ISP (pavyzdys)
```java interface Printer { void print(); } interface Scanner { void scan(); } interface Fax { void fax(); } class SimplePrinter implements Printer { @Override public void print() { // Printing logic } } class MultiFunctionPrinter implements Printer, Scanner, Fax { @Override public void print() { // Printing logic } @Override public void scan() { // Scanning logic } @Override public void fax() { // Faxing logic } } ```Dabar `SimplePrinter` klasė įgyvendina tik `Printer` sąsają, kurios jai ir tereikia. `MultiFunctionPrinter` klasė įgyvendina visas tris sąsajas, suteikdama visą funkcionalumą.
Praktiniai patarimai
- Suskaidykite dideles sąsajas į mažesnes, labiau orientuotas sąsajas.
- Užtikrinkite, kad klientai priklausytų tik nuo jiems reikalingų metodų.
- Venkite kurti monolitinių sąsajų, kurios verčia klientus įgyvendinti nereikalingus metodus.
5. Priklausomybių inversijos principas (DIP)
Apibrėžimas
Priklausomybių inversijos principas teigia, kad aukšto lygio moduliai neturėtų priklausyti nuo žemo lygio modulių. Abu turėtų priklausyti nuo abstrakcijų. Abstrakcijos neturėtų priklausyti nuo detalių. Detalės turėtų priklausyti nuo abstrakcijų.
Paaiškinimas ir nauda
DIP skatina silpną susiejimą ir palengvina sistemos keitimą bei testavimą. Aukšto lygio moduliai (pvz., verslo logika) neturėtų priklausyti nuo žemo lygio modulių (pvz., duomenų prieigos). Vietoj to, abu turėtų priklausyti nuo abstrakcijų (pvz., sąsajų). Tai leidžia lengvai pakeisti skirtingas žemo lygio modulių implementacijas nepaveikiant aukšto lygio modulių. Tai taip pat palengvina vienetinių testų rašymą, nes galite imituoti (mock) arba pakeisti (stub) žemo lygio priklausomybes.
Pavyzdys
Apsvarstykite klasę pavadinimu `UserManager`, kuri priklauso nuo konkrečios klasės `MySQLDatabase`, kad saugotų vartotojo duomenis.
DIP pažeidimas (pavyzdys)
```java class MySQLDatabase { public void saveUser(String username, String password) { // Save user data to MySQL database } } class UserManager { private MySQLDatabase database; public UserManager() { this.database = new MySQLDatabase(); } public void createUser(String username, String password) { // Validate user data database.saveUser(username, password); } } ```Šiame pavyzdyje `UserManager` klasė yra glaudžiai susieta su `MySQLDatabase` klase. Jei norėtume pereiti prie kitos duomenų bazės (pvz., PostgreSQL), turėtume modifikuoti `UserManager` klasę, pažeisdami DIP.
Norėdami laikytis DIP, galime įvesti sąsają pavadinimu `Database`, kuri apibrėžia `saveUser()` metodą. Tuomet `UserManager` klasė priklauso nuo `Database` sąsajos, o ne nuo konkrečios `MySQLDatabase` klasės.
Laikantis DIP (pavyzdys)
```java interface Database { void saveUser(String username, String password); } class MySQLDatabase implements Database { @Override public void saveUser(String username, String password) { // Save user data to MySQL database } } class PostgreSQLDatabase implements Database { @Override public void saveUser(String username, String password) { // Save user data to PostgreSQL database } } class UserManager { private Database database; public UserManager(Database database) { this.database = database; } public void createUser(String username, String password) { // Validate user data database.saveUser(username, password); } } ```Dabar `UserManager` klasė priklauso nuo `Database` sąsajos, ir mes galime lengvai keisti skirtingas duomenų bazių implementacijas nekeisdami `UserManager` klasės. Tai galime pasiekti per priklausomybių įterpimą (dependency injection).
Praktiniai patarimai
- Priklausykite nuo abstrakcijų, o ne nuo konkrečių implementacijų.
- Naudokite priklausomybių įterpimą, kad pateiktumėte priklausomybes klasėms.
- Venkite kurti priklausomybes nuo žemo lygio modulių aukšto lygio moduliuose.
SOLID principų naudojimo nauda
Laikantis SOLID principų gaunama daug naudos, įskaitant:
- Geresnis palaikomumas: SOLID kodą lengviau suprasti ir modifikuoti, sumažinant klaidų įvedimo riziką.
- Patobulintas pakartotinis naudojimas: SOLID kodas yra moduliškesnis ir gali būti pakartotinai naudojamas kitose programos dalyse.
- Geresnis testuojamumas: SOLID kodą lengviau testuoti, nes priklausomybes galima lengvai imituoti ar pakeisti.
- Sumažintas susiejimas: SOLID principai skatina silpną susiejimą, todėl sistema tampa lankstesnė ir atsparesnė pokyčiams.
- Geresnis plečiamumas: SOLID kodas yra sukurtas taip, kad būtų plečiamas, leidžiant sistemai augti ir prisitaikyti prie kintančių reikalavimų.
Išvados
SOLID principai yra esminės gairės kuriant patikimą, palaikomą ir plečiamą objektinio programavimo programinę įrangą. Suprasdami ir taikydami šiuos principus, programuotojai gali sukurti sistemas, kurias lengviau suprasti, testuoti ir modifikuoti. Nors iš pradžių jie gali atrodyti sudėtingi, SOLID principų laikymosi nauda gerokai viršija pradinę mokymosi kreivę. Taikykite šiuos principus savo programinės įrangos kūrimo procese ir būsite pakeliui į geresnės programinės įrangos kūrimą.
Atminkite, kad tai yra gairės, o ne griežtos taisyklės. Kontekstas yra svarbus, ir kartais šiek tiek nukrypti nuo principo yra būtina norint rasti pragmatišką sprendimą. Tačiau siekis suprasti ir taikyti SOLID principus neabejotinai pagerins jūsų programinės įrangos projektavimo įgūdžius ir kodo kokybę.