Ceļvedis par objektorientētās projektēšanas SOLID principiem, skaidrojot katru ar piemēriem un padomiem ilgtspējīgas un mērogojamas programmatūras izstrādei.
SOLID principi: Objekta orientēta dizaina vadlīnijas stabilai programmatūrai
Programmatūras izstrādes pasaulē robustu, uzturamu un mērogojamu lietojumprogrammu izveide ir vissvarīgākā. Objektorientētā programmēšana (OOP) piedāvā spēcīgu paradigmu šo mērķu sasniegšanai, taču ir būtiski ievērot noteiktus principus, lai izvairītos no sarežģītu un trauslu sistēmu izveides. SOLID principi, piecas fundamentālas vadlīnijas, nodrošina ceļvedi programmatūras projektēšanai, kas ir viegli saprotama, pārbaudāma un modificējama. Šis visaptverošais ceļvedis detalizēti aplūko katru principu, piedāvājot praktiskus piemērus un ieskatus, lai palīdzētu jums izveidot labāku programmatūru.
Kas ir SOLID principi?
SOLID principus ieviesa Roberts K. Mārtins (pazīstams arī kā "Uncle Bob") un tie ir objektorientēta dizaina stūrakmens. Tie nav stingri noteikumi, bet drīzāk vadlīnijas, kas palīdz izstrādātājiem radīt uzturamāku un elastīgāku kodu. Akronīms SOLID apzīmē:
- S - Vienas atbildības princips
- O - Atvērtības/Slēgtības princips
- L - Liskovas aizstājamības princips
- I - Saskarņu segregācijas princips
- D - Atkarības inversijas princips
Ieskatīsimies katrā principā un izpētīsim, kā tie veicina labāku programmatūras dizainu.
1. Vienas atbildības princips (SRP)
Definīcija
Vienas atbildības princips nosaka, ka klasei vajadzētu būt tikai vienam iemeslam, kāpēc tā mainās. Citiem vārdiem sakot, klasei vajadzētu būt tikai vienam uzdevumam vai atbildībai. Ja klasei ir vairākas atbildības, tā kļūst cieši saistīta un grūti uzturama. Jebkuras izmaiņas vienā atbildībā var netīši ietekmēt citas klases daļas, izraisot neparedzētas kļūdas un palielinot sarežģītību.
Skaidrojums un ieguvumi
Galvenais ieguvums, ievērojot SRP, ir paaugstināta modularitāte un uzturamība. Ja klasei ir viena atbildība, to ir vieglāk saprast, testēt un modificēt. Izmaiņām ir mazāka iespēja radīt neparedzētas sekas, un klasi var atkārtoti izmantot citās lietojumprogrammas daļās, neieviešot nevajadzīgas atkarības. Tas arī veicina labāku koda organizāciju, jo klases ir vērstas uz konkrētiem uzdevumiem.
Piemērs
Apsveriet klasi ar nosaukumu `User`, kas apstrādā gan lietotāju autentifikāciju, gan lietotāju profilu pārvaldību. Šī klase pārkāpj SRP, jo tai ir divas atšķirīgas atbildības.
Pārkāpjot SRP (Piemērs)
```java public class User { public void authenticate(String username, String password) { // Autentifikācijas loģika } public void changePassword(String oldPassword, String newPassword) { // Paroles maiņas loģika } public void updateProfile(String name, String email) { // Profila atjaunināšanas loģika } } ```Lai ievērotu SRP, mēs varam nodalīt šīs atbildības dažādās klasēs:
Ievērojot SRP (Piemērs)Šajā pārskatītajā dizainā `UserAuthenticator` apstrādā lietotāju autentifikāciju, savukārt `UserProfileManager` apstrādā lietotāju profilu pārvaldību. Katrai klasei ir viena atbildība, padarot kodu modulārāku un vieglāk uzturamu.
Praktiski padomi
- Identificējiet klases dažādās atbildības.
- Nodalīt šīs atbildības dažādās klasēs.
- Nodrošiniet, lai katrai klasei būtu skaidrs un labi definēts mērķis.
2. Atvērtības/Slēgtības princips (OCP)
Definīcija
Atvērtības/Slēgtības princips nosaka, ka programmatūras entītijām (klasēm, moduļiem, funkcijām utt.) jābūt atvērtām paplašināšanai, bet slēgtām modifikācijai. Tas nozīmē, ka jāspēj pievienot jaunu funkcionalitāti sistēmai, nemodificējot esošo kodu.
Skaidrojums un ieguvumi
OCP ir izšķirošs faktors uzturamas un mērogojamas programmatūras izveidei. Kad jāpievieno jaunas funkcijas vai uzvedības, nevajadzētu modificēt esošo kodu, kas jau darbojas pareizi. Esošā koda modificēšana palielina kļūdu ieviešanas risku un esošās funkcionalitātes sabrukumu. Ievērojot OCP, var paplašināt sistēmas funkcionalitāti, neietekmējot tās stabilitāti.
Piemērs
Apsveriet klasi ar nosaukumu `AreaCalculator`, kas aprēķina dažādu figūru laukumu. Sākotnēji tā var atbalstīt tikai taisnstūru laukuma aprēķināšanu.
Pārkāpjot OCP (Piemērs)Ja mēs vēlamies pievienot atbalstu apļu laukuma aprēķināšanai, mums jāmodificē klase `AreaCalculator`, pārkāpjot OCP.
Lai ievērotu OCP, mēs varam izmantot interfeisu vai abstraktu klasi, lai definētu kopīgu `area()` metodi visām figūrām.
Ievērojot OCP (Piemērs)
```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(); } } ```Tagad, lai pievienotu atbalstu jaunai figūrai, mums vienkārši jāizveido jauna klase, kas implementē interfeisu `Shape`, nemodificējot klasi `AreaCalculator`.
Praktiski padomi
- Izmantojiet interfeisus vai abstraktas klases, lai definētu kopīgas uzvedības.
- Projektējiet savu kodu tā, lai tas būtu paplašināms, izmantojot mantošanu vai kompozīciju.
- Izvairieties modificēt esošo kodu, pievienojot jaunu funkcionalitāti.
3. Liskovas aizstājamības princips (LSP)
Definīcija
Liskovas aizstājamības princips nosaka, ka apakštipiem jābūt aizstājamiem ar to bāzes tipiem, nemainot programmas pareizību. Vienkāršāk sakot, ja jums ir bāzes klase un atvasināta klase, jums vajadzētu būt iespējai izmantot atvasināto klasi jebkur, kur tiek izmantota bāzes klase, neizraisot neparedzētu uzvedību.
Skaidrojums un ieguvumi
LSP nodrošina, ka mantošana tiek izmantota pareizi un ka atvasinātās klases uzvedas konsekventi ar savām bāzes klasēm. LSP pārkāpšana var izraisīt neparedzētas kļūdas un apgrūtināt sistēmas uzvedības izpratni. LSP ievērošana veicina koda atkārtotu izmantojamību un uzturamību.
Piemērs
Apsveriet bāzes klasi ar nosaukumu `Bird` ar metodi `fly()`. Atvasinātā klase ar nosaukumu `Penguin` manto no `Bird`. Tomēr pingvīni nevar lidot.
Pārkāpjot LSP (Piemērs)Šajā piemērā klase `Penguin` pārkāpj LSP, jo tā ignorē metodi `fly()` un izmet izņēmumu. Ja mēģināsiet izmantot `Penguin` objektu vietā, kur tiek sagaidīts `Bird` objekts, saņemsiet neparedzētu izņēmumu.
Lai ievērotu LSP, mēs varam ieviest jaunu interfeisu vai abstraktu klasi, kas apzīmē lidojošos putnus.
Ievērojot LSP (Piemērs)Tagad tikai klases, kas var lidot, implementē interfeisu `FlyingBird`. Klase `Penguin` vairs nepārkāpj LSP.
Praktiski padomi
- Nodrošiniet, lai atvasinātās klases uzvedas konsekventi ar savām bāzes klasēm.
- Izvairieties izmest izņēmumus pārrakstītās metodēs, ja bāzes klase tos neizmet.
- Ja atvasināta klase nevar implementēt metodi no bāzes klases, apsveriet cita dizaina izmantošanu.
4. Saskarņu segregācijas princips (ISP)
Definīcija
Saskarņu segregācijas princips nosaka, ka klientiem nevajadzētu būt spiestiem būt atkarīgiem no metodēm, kuras tie neizmanto. Citiem vārdiem sakot, interfeisam jābūt pielāgotam klienta īpašajām vajadzībām. Lieli, monolīti interfeisi jāsadala mazākos, mērķtiecīgākos interfeisos.
Skaidrojums un ieguvumi
ISP novērš to, ka klientiem tiek uzspiests implementēt metodes, kuras tiem nav vajadzīgas, tādējādi samazinot savienojamību un uzlabojot koda uzturamību. Kad interfeiss ir pārāk liels, klienti kļūst atkarīgi no metodēm, kas nav saistītas ar viņu specifiskajām vajadzībām. Tas var izraisīt nevajadzīgu sarežģītību un palielināt kļūdu ieviešanas risku. Ievērojot ISP, var izveidot mērķtiecīgākus un atkārtoti izmantojamus interfeisus.
Piemērs
Apsveriet lielu interfeisu ar nosaukumu `Machine`, kas definē metodes drukāšanai, skenēšanai un faksa sūtīšanai.
Pārkāpjot ISP (Piemērs)
```java interface Machine { void print(); void scan(); void fax(); } class SimplePrinter implements Machine { @Override public void print() { // Drukāšanas loģika } @Override public void scan() { // Šis printeris nevar skenēt, tāpēc izmetam izņēmumu vai atstājam to tukšu throw new UnsupportedOperationException(); } @Override public void fax() { // Šis printeris nevar sūtīt faksu, tāpēc izmetam izņēmumu vai atstājam to tukšu throw new UnsupportedOperationException(); } } ```Klasei `SimplePrinter` ir jāimplementē tikai metode `print()`, taču tā ir spiesta implementēt arī metodes `scan()` un `fax()`, pārkāpjot ISP.
Lai ievērotu ISP, mēs varam sadalīt interfeisu `Machine` mazākos interfeisos:
Ievērojot ISP (Piemērs)
```java interface Printer { void print(); } interface Scanner { void scan(); } interface Fax { void fax(); } class SimplePrinter implements Printer { @Override public void print() { // Drukāšanas loģika } } class MultiFunctionPrinter implements Printer, Scanner, Fax { @Override public void print() { // Drukāšanas loģika } @Override public void scan() { // Skenēšanas loģika } @Override public void fax() { // Faksa sūtīšanas loģika } } ```Tagad klase `SimplePrinter` implementē tikai interfeisu `Printer`, kas ir viss, kas tai ir nepieciešams. Klase `MultiFunctionPrinter` implementē visus trīs interfeisus, nodrošinot pilnu funkcionalitāti.
Praktiski padomi
- Sadalīt lielus interfeisus mazākos, mērķtiecīgākos interfeisos.
- Nodrošiniet, lai klienti būtu atkarīgi tikai no metodēm, kuras tiem ir nepieciešamas.
- Izvairieties veidot monolītus interfeisus, kas liek klientiem implementēt nevajadzīgas metodes.
5. Atkarības inversijas princips (DIP)
Definīcija
Atkarības inversijas princips nosaka, ka augsta līmeņa moduļiem nevajadzētu būt atkarīgiem no zema līmeņa moduļiem. Abiem jābūt atkarīgiem no abstrakcijām. Abstrakcijām nevajadzētu būt atkarīgām no detaļām. Detaļām jābūt atkarīgām no abstrakcijām.
Skaidrojums un ieguvumi
DIP veicina vaļīgu savienojumu un atvieglo sistēmas maiņu un testēšanu. Augsta līmeņa moduļiem (piemēram, biznesa loģikai) nevajadzētu būt atkarīgiem no zema līmeņa moduļiem (piemēram, datu piekļuves). Tā vietā abiem jābūt atkarīgiem no abstrakcijām (piemēram, interfeisiem). Tas ļauj viegli mainīt dažādas zema līmeņa moduļu implementācijas, neietekmējot augsta līmeņa moduļus. Tas arī atvieglo vienības testu rakstīšanu, jo var izveidot zema līmeņa atkarību imitācijas vai aizstājējus.
Piemērs
Apsveriet klasi ar nosaukumu `UserManager`, kas ir atkarīga no konkrētas klases ar nosaukumu `MySQLDatabase`, lai glabātu lietotāja datus.
Pārkāpjot DIP (Piemērs)
```java class MySQLDatabase { public void saveUser(String username, String password) { // Saglabāt lietotāja datus MySQL datubāzē } } class UserManager { private MySQLDatabase database; public UserManager() { this.database = new MySQLDatabase(); } public void createUser(String username, String password) { // Validēt lietotāja datus database.saveUser(username, password); } } ```Šajā piemērā klase `UserManager` ir cieši saistīta ar klasi `MySQLDatabase`. Ja mēs vēlamies pārslēgties uz citu datubāzi (piemēram, PostgreSQL), mums jāmodificē klase `UserManager`, pārkāpjot DIP.
Lai ievērotu DIP, mēs varam ieviest interfeisu ar nosaukumu `Database`, kas definē metodi `saveUser()`. Pēc tam klase `UserManager` ir atkarīga no interfeisa `Database`, nevis no konkrētās klases `MySQLDatabase`.
Ievērojot DIP (Piemērs)
```java interface Database { void saveUser(String username, String password); } class MySQLDatabase implements Database { @Override public void saveUser(String username, String password) { // Saglabāt lietotāja datus MySQL datubāzē } } class PostgreSQLDatabase implements Database { @Override public void saveUser(String username, String password) { // Saglabāt lietotāja datus PostgreSQL datubāzē } } class UserManager { private Database database; public UserManager(Database database) { this.database = database; } public void createUser(String username, String password) { // Validēt lietotāja datus database.saveUser(username, password); } } ```Tagad klase `UserManager` ir atkarīga no interfeisa `Database`, un mēs varam viegli pārslēgties starp dažādām datubāzes implementācijām, nemodificējot klasi `UserManager`. To var panākt ar atkarību injekcijas palīdzību.
Praktiski padomi
- Esiet atkarīgi no abstrakcijām, nevis no konkrētām implementācijām.
- Izmantojiet atkarību injekciju, lai nodrošinātu atkarības klasēm.
- Izvairieties veidot atkarības no zema līmeņa moduļiem augsta līmeņa moduļos.
SOLID principu izmantošanas priekšrocības
SOLID principu ievērošana piedāvā daudzas priekšrocības, tostarp:
- Palielināta uzturamība: SOLID kodu ir vieglāk saprast un modificēt, samazinot kļūdu ieviešanas risku.
- Uzlabota atkārtota izmantojamība: SOLID kods ir modulārāks un to var atkārtoti izmantot citās lietojumprogrammas daļās.
- Uzlabota testējamība: SOLID kodu ir vieglāk testēt, jo atkarības var viegli imitēt vai aizstāt.
- Samazināta savienojamība: SOLID principi veicina vaļīgu savienojumu, padarot sistēmu elastīgāku un noturīgāku pret izmaiņām.
- Palielināta mērogojamība: SOLID kods ir izstrādāts tā, lai tas būtu paplašināms, ļaujot sistēmai augt un pielāgoties mainīgām prasībām.
Secinājums
SOLID principi ir būtiskas vadlīnijas robustas, uzturamas un mērogojamas objektorientētas programmatūras izveidei. Izprotot un pielietojot šos principus, izstrādātāji var radīt sistēmas, kas ir vieglāk saprotamas, testējamas un modificējamas. Lai gan sākumā tie var šķist sarežģīti, SOLID principu ievērošanas ieguvumi ievērojami pārsniedz sākotnējo mācīšanās līkni. Ieviesiet šos principus savā programmatūras izstrādes procesā, un jūs būsiet ceļā uz labākas programmatūras veidošanu.
Atcerieties, ka šīs ir vadlīnijas, nevis stingri noteikumi. Kontekstam ir nozīme, un dažreiz ir nepieciešams nedaudz pielāgot principu pragmatiska risinājuma dēļ. Tomēr centieni izprast un pielietot SOLID principus neapšaubāmi uzlabos jūsu programmatūras projektēšanas prasmes un koda kvalitāti.