- Načela SOLID opredeljujejo pet smernic objektno usmerjenega oblikovanja, ki izboljšujejo jasnost kode, razširljivost in preizkušljivost.
- Uporabite SRP, OCP, LSP, ISP in DIP v Python Vključuje ločevanje odgovornosti, zanašanje na abstrakcije in oblikovanje koherentnih hierarhij.
- SOLID zasnova je kombinirana z DRY, KISS in YAGNI za doseganje vzdržnih sistemov brez pretiranega načrtovanja, prilagojenih resničnemu kontekstu projekta.

V naslednjih vrsticah si bomo podrobno ogledali, kaj je SOLID, od kod izvira, Za kaj se uporablja v vsakodnevni uporabi Pythona in kako ga uporabiti na primerih iz resničnega sveta? v tem jeziku. Poleg tega bomo vključili še druga ključna načela, kot so DRY, KISS in YAGNI, ter razpravljali o tem, v katerih kontekstih se morda ne splača biti ultra-purističen s SOLID.
Kaj je SOLID in od kod prihaja?
Ko govorimo o SOLID-u, mislimo na pet klasičnih načel objektno usmerjenega oblikovanja ki se osredotočajo na to, kako strukturirati razrede, module in vmesnike, da bi bilo programsko opremo enostavno razširiti, preizkusiti in vzdrževati.
Ta načela so postala priljubljena zaradi Robert C. Martin (stric Bob), eden od očetov agilnega razvoja in avtor knjig, kot je Čista koda o Čista arhitekturaV devetdesetih in zgodnjih 2000-ih je objavil več člankov o objektno usmerjenem oblikovanju, vključno z "Principi objektno usmerjenega oblikovanja" in "Principi oblikovanja in vzorci oblikovanja".
Kasneje je inženir Michael Feathers predlagal kratico SOLID da se sklicuje na sklop petih najpomembnejših načel, ki so izhajala iz teh del. Ideja je bila imeti preprosto mnemonično pravilo ki bi si jih lahko zapomnil vsak razvijalec in jih uporabil pri načrtovanju svojega razreda.
Kratica pomeni:
- S - Načelo enotne odgovornosti (SRP, načelo enotne odgovornosti).
- O - Načelo odprto-zaprto (OCP, načelo odprto/zaprto).
- L - Liskovo načelo substitucije (LSP, Liskovo načelo substitucije).
- I - Načelo ločevanja vmesnikov (ISP, načelo ločevanja vmesnikov).
- D - Načelo inverzije odvisnosti (DIP, načelo obrata odvisnosti).
Čeprav so bili prvotno oblikovani z mislijo na močno tipizirane jezike, kot je na primer Java ali C#Ta načela so popolnoma uporabna za Python, C++, PHP in na splošno za kateri koli jezik s podporo za objektno orientacijo.

Za kaj se načela SOLID uporabljajo v projektih iz resničnega sveta?
Poleg teorije je SOLID še posebej opazen, ko projekt raste in Več razvijalcev se dotika iste kode več mesecev ali let.Uporaba teh načel ima več zelo očitnih prednosti:
Po eni strani izboljšujejo berljivost in jasnost arhitektureRazredi imajo definirane odgovornosti, odvisnosti so nadzorovane in lažje je slediti poslovnemu toku, ne da bi se izgubili v podrobnostih infrastrukture.
Poleg tega dajejo prednost ponovna uporaba in razširljivost kode. Če so razredi dobro zasnovani, dodajanje novih funkcij ne pomeni razgradnje polovice sistema. Tendenca je, da se sistem razširi z novimi implementacijami, namesto da se prepiše tisto, kar že deluje in je preizkušeno.
Druga ključna točka je preizkušljivostZ zmanjšanjem sklopitve in zanašanjem na abstrakcije postane preprostejše. injicirajte dvojni test (mocki, stubi) in napišite hitre enotne teste, ki niso odvisni od podatkovnih baz pravi, omrežni klici ali drugi zunanji sistemi.
Končno, kodna baza, ki se drži načel SOLID, manj trpi zaradi smrdi kode, gnitje kode in »špagetna koda«Z drugimi besedami, manj se razgrajuje z časKopiči manj tehničnega dolga in je varnejši in stabilnejši v kritičnih okoljih.
S – Načelo ene same odgovornosti v Pythonu
Načelo ene same odgovornosti pravi, da Razred bi moral imeti en sam razlog za spremembo.To ne pomeni, da počne eno samo mikroskopsko stvar, temveč da je odgovoren za dobro definirano področje funkcionalnosti.
Ko razred meša poslovno logiko, dostop do podatkov, oblikovanje poročil in predstavitveno logiko, vsaka sprememba na katerem koli od teh področij zahteva spreminjanje istega dela kode. To poveča tveganje za križne napake in konflikti med ekipami.
Pomislite na primer na razred uporabnik V Pythonu je ta razred poleg predstavljanja uporabnika odgovoren tudi za povezavo z bazo podatkov, shranjevanje uporabniških podatkov in ustvarjanje besedilnih poročil. Ta razred bo morda treba spremeniti iz več razlogov: če se spremenijo atributi uporabnika, če se spremeni baza podatkov ali če se spremeni oblika poročila.
Alternativa, usklajena s SRP, je ločitev odgovornosti: en razred ali podatkovni razred za domenski model uporabnika, drugega za vztrajnost (repozitorij ali prehod) in drugega za ustvarjanje poročil ali pogledovVsaka sprememba pade v ustrezni del, kar zmanjša sklopko.
Nekaj podobnega lahko vidimo na klasičnem primeru v Pythonu: razred Raca ki definira raco in hkrati upravlja pogovore med racami z metodo greet()Ta komunikacijski del je mogoče izvleči v objekt komunikatortako da raca zna le leteti, plavati in oddajati zvoke, komunikator pa zna artikulirati dialog med dvema pticama.
Ta pristop se zelo dobro ujema z drugimi načeli, kot so DRY (brez ponavljanja logike v več razredih) in z idejo o ohranjanju visoka kohezija znotraj vsakega modula: vse njegove funkcije in atributi kažejo na isto poslovno odgovornost.
O – Načelo odprto/zaprto, uporabljeno za razširitve vedenja
Načelo odprto/zaprto pravi, da Programske entitete bi morale biti odprte za razširitve, vendar zaprte za modifikacijeV vsakdanjem smislu: morali bi biti sposobni dodati nove različice vedenja, ne da bi morali urejati kodo, ki že deluje.
Tipičen anti-vzorec v Pythonu je razred z metodo, ki določa vedenje z if/elif velikan, odvisno od vrste: na primer, Kalkulator površine ki preverja, ali je objekt Rectangle, Circle, Triangle in tako naprej. Vsaka nova geometrijska oblika sili k dodajanju nove veje metodi in zahteva spreminjanje razreda, ki je bil že preizkušen.
Spoštovanje OCP vključuje programiranje proti abstrakcijamV tem primeru definiramo abstraktni razred. Shape z metodo area() in vsako obliko (pravokotnik, krog, trikotnik…) implementiramo z implementacijo te metode. Kalkulator površine preprosto pokliče shape.area() ne da bi vedel, s katero specifično vrsto se sooča.
Če nadaljujemo s Pythonom, je še en primer, obravnavan v različnih virih, primer objekta komunikator ki se uporablja za pogovore med pticami. Če želimo podpreti različne oblike pogovora (preproste, formalne, z emoji itd.), namesto spreminjanja metode communicate() vsakič definiramo abstrakcija pogovora (npr. AbstractConversation) in ustvarili smo implementacije, kot so SimpleConversation ki generirajo fraze. Komunikator preprosto izpiše, kar vrne pogovor, ne da bi spreminjal lastno kodo.
V poslovnem svetu je OCP ključnega pomena za zmanjšanje tveganja pri uvajanju sprememb: preprečuje ponovno odpiranje pogosto uporabljenih osnovnih razredov in spodbuja ... razširitev z novimi podrazredi ali strategijami, pri čemer stabilna koda ostane čim bolj nedotaknjena.
L – Liskovo načelo substitucije in pravilno dedovanje
Liskovo načelo substitucije pravi, da Objekti podrazreda bi morali biti sposobni nadomestiti objekte svojega nadrazreda, ne da bi pri tem porušili sistem.Z drugimi besedami, če koda pričakuje primerek osnovnega razreda, bi morala delovati enako, če ji damo primerek katerega koli podrejenega razreda.
V praksi to pomeni, da podrazred ne bi smel kršitev pričakovanj ali pogodbe ki definira osnovni razred: tipi vrnitev, izjeme, predpogoji in postpogoji morajo ostati dosledni.
Znan (in zavajajoč) primer je hierarhija Pravokotnik / KvadratMatematično je kvadrat pravokotnik, vendar to v kodi ni vedno priročno. Square podedovano neposredno od RectangleČe ima pravokotnik ločena nastavka za višino in širino, lahko metoda testiranja predpostavlja, da spreminjanje samo višine ohrani širino enako. Če razred kvadrat prisili oba, da se spremenita hkrati, se pogodba nadrazreda nenadoma prekine.
Nekaj podobnega se dogaja v hierarhijah, kot je Bird V Pythonu. Če osnovni razred vključuje metodo fly()Potem pa imamo podrazrede, kot so Ostrich ali pingvine, ki ne morejo leteti, vsilite to implementacijo in zaženite NotImplementedError prekine LSP. Vsaka funkcija, ki prejme Bird in pokličite fly() To bi moral biti sposoben storiti brez presenetljivih izjem.
Rešitev vključuje preoblikovanje hierarhije: ohrani se generični osnovni razred Bird brez metode fly()in vmesni podrazredi, kot so Leteča ptica y Plavajoča pticaPtice, ki letijo, izvajajo prvo; tiste, ki plavajo, drugo; tiste, ki letijo, pa dedujejo obe. Na ta način noben podrazred ni prisiljen ponuditi vedenja, ki zanj nima smisla.
Spoštovanje LSP zahteva skrbno preučitev dednih odnosov v Pythonu in v mnogih primerih vodi do dajanja prednosti preprosta sestava in vmesniki v primeru globokih dreves dedovanja, kjer je enostavno kršiti pogodbo nadrazreda.
I – Načelo ločevanja vmesnikov in bolj specifični razredi
Načelo ločevanja vmesnikov pravi, da Stranke ne smemo siliti, da se zanaša na metode, ki jih ne uporablja.V praksi se to prevede v oblikovanje majhnih, specifičnih vmesnikov (ali abstraktnih razredov v Pythonu) namesto enega velikanskega vmesnika za vse.
Predstavljajte si vmesnik Delavec v Pythonu, ki definira metode work() y eat()Ta vmesnik je smiseln za človeka, ki dela in je, ne pa za robota, ki samo dela. Če razred prisilimo Robot izvajati eat(), se bo končala s prazno metodo ali izjemo, kar pomeni, da » roboti "Ne jedo," kar je jasen znak kršitve s strani ponudnika internetnih storitev.
Rešitev vključuje razdelitev tega vmesnika na Izvedljivo (nekaj, kar lahko deluje) in Užiten (nekaj, kar lahko pojeste). Tako je razred Human izvaja oboje, medtem ko Robot Implementira samo delovni del. Vsaka stranka se zanaša le na metode, ki jih dejansko potrebuje.
Če se vrnemo k pticam, se nekaj podobnega zgodi z vmesnikom, ki združuje metode, kot so fly() y swim()Vse ptice ne počnejo obeh stvari, zato jih je bolj smiselno ločiti na Leteča ptica y Plavajoča pticaNastala zasnova je bolj prilagodljiva in se izognemo polnjenju nepomembnih metod z navidezno kodo.
V Pythonu, čeprav nimamo formalnih vmesnikov kot v Javi, lahko dosežemo enak učinek z uporabo abstraktni razredi modula abc ali protokole tipkanja. Ločevanje vmesnikov naredi vsak razred bolj definiran in zmanjša verjetnost uvedbe nepotrebnih odvisnosti.
D – Načelo inverzije odvisnosti in oblikovanje na osnovi abstrakcije
Načelo obrata odvisnosti poudarja dve ključni ideji: Visokonivojski moduli ne smejo biti odvisni od nizkonivojskih modulov, In oboje mora biti odvisno od abstrakcijPoleg tega abstrakcije ne bi smele biti odvisne od podrobnosti; podrobnosti so odvisne od abstrakcij.
Zelo pogosta napaka je, da domenski ali poslovni logični razred v Pythonu neposredno ustvari konkreten primerek baze podatkov, storitve HTTP ali odjemalca za sporočanje: na primer Uporabniški repozitorij to notranje počne self.database = MySQLDatabase()Če bomo jutri želeli za testiranje preklopiti na PostgreSQL ali mehanizem v pomnilniku, bomo morali ta razred urediti.
Uporaba DIP vključuje uvedbo abstrakcija baze podatkov (na primer abstraktni razred) Database z metodami connect() y query()) in ustvariti konkretne izvedbe, kot so MySQLDatabase o PostgreSQLDatabaseRepozitorij preneha ustvarjati instance baze podatkov in jo začne prejemati od zunaj, običajno prek konstruktorja.
Ta vzorec je znan kot injekcija odvisnosti In to je eden od stebrov za doseganje modularne in preizkušljive kode: v produkciji vbrizgamo resnično implementacijo; v testiranju vbrizgamo testni dvojnik, ki simulira vedenje brez dotikanja zunanjih sistemov.
V bogatejšem primeru lahko abstrakcijo komunikacijskega kanala definiramo kot Abstraktni kanalin abstrakcija komunikatorja, Abstraktni komunikator, ki sodeluje z PovzetekPogovorNamesto tega SMSCommunicator notranje ustvarja SMSChannelRazličica, usklajena z DIP, zagotavlja, da komunikator v konstruktorju prejme abstrakten kanal. To pomeni, da ni odvisen od konkretnega razreda, temveč od vmesnika, in lahko deluje s katero koli vrsto kanala, ki izpolnjuje pogodbo.
Skratka, DIP spodbuja načrtovanje plasti, kjer poslovna logika temelji na generičnih vmesnikih, ogrodja, konfiguracije ali tovarne pa so odgovorne za povezovanje teh vmesnikov s specifičnimi implementacijami, odvisno od okolja.
Praktičen primer v Pythonu: storitev obveščanja SOLID
Zelo ilustrativen primer, ki prikazuje, kako se združuje več oblikovalskih načel, je primer sistem obveščanja ki lahko pošilja sporočila prek različnih kanalov, kot sta e-pošta ali SMS, in ki ga je mogoče enostavno razširiti z novimi sredstvi (push sporočila, spletni kavlji itd.).
Cilj je imeti glavno logiko, ki se odloči "Moram obvestiti X stvar" ni treba prepisati vsakič, ko dodamo kanal, in da ga lahko preizkusimo, ne da bi med preizkusi sprožili dejanska e-poštna sporočila ali SMS-e.
Da bi to naredili, definiramo abstrakcijo Obveščevalec z metodo send(to, subject, body) ki vrne logično vrednost, ki označuje uspeh ali neuspeh. Na podlagi tega smo ustvarili implementacije, kot je Obveščalnik o e-pošti y SMSNotifiervsak, ki v svojem konstruktorju prejme zunanjega odjemalca (na primer, smtp_client o sms_client) zadolžen za pogovore s kraljevo službo.
Poleg vsega tega, a Storitev obveščanja ki prejme seznam prijaviteljev (vse, kar izvaja pogodbo) in ponudi metodo za pošlji obvestilo po vseh kanalihTa storitev ne ve ničesar o SMTP, ponudnikih SMS ali HTTP API-jih: samo ponavlja in kliče send() o vsakem prijavitelju.
Ta zasnova jasno prikazuje več načel delovanja:
- SRPVsak razred ima zelo omejeno odgovornost: a
EmailNotifierObdeluje samo e-pošto,SMSNotifierSamo SMS, orkestrska služba in zunanje stranke vedo samo za prevoz. - DIP:
NotificationServiceOdvisno od abstrakcijeNotifierne specifičnih razredov, obveščalci pa so nato odvisni od vbrizganih odjemalcev. - OCP: dodati
PushNotifieroWebhookNotifierNi se treba dotikatiNotificationServicePreprosto ustvarite novo implementacijo in jo registrirajte. - LSP in ISPvse izvedbe od
NotifierSpoštujejo isto pogodbosend(), brez obveznih metod, ki za določen kanal nimajo smisla.
Z vidika testiranja je dovolj ustvariti enega ali več DummyNotifier ki snemajo klice na send() brez da bi storili karkoli drugega. Z vstavljanjem v storitev lahko preverimo, ali so bila vsa obvestila sprožena, brez potrebe po omrežjih, čakalnih vrstah ali zunanjih sistemih.
Ta primer je mogoče dopolniti tudi z načeli, kot so DRY (izogibajte se podvajanju logike običajnega formata), KISS (ne uvajajte ponovnih poskusov, čakalnih vrst ali določanja prioritet, dokler to ni resnično potrebno) in YAGNI (Ne pričakujte funkcij, ki niso v našem neposrednem načrtu.)
Razmerje med SOLID-om in drugimi oblikovalskimi načeli
Čeprav se SOLID osredotoča na objektno usmerjeno načrtovanje, se v praksi kombinira z drugimi splošnimi načeli načrtovanja programske opreme, kot so SUHO, POLJUB in YAGNIki se pogosto pojavljajo tudi v literaturi o čisti kodi.
SUHO (ne ponavljaj se) Ne pozabite, da bi morala imeti pomembna poslovna logika v sistemu samo eno predstavitev. To se popolnoma ujema s SRP in OCP: če ponavljamo vedenje v različnih razredih, razširitev ali popravljanje primera uporabe zahteva spreminjanje številnih mest in poveča tveganje za nedoslednosti.
KISS (Keep It Simple, Stupid) Spodbuja nas, da se izogibamo nepotrebno zapletenim arhitekturam in abstrakcijam. Čeprav se morda zdi protislovno, uporaba SOLID-a ne pomeni polnjenja projekta z vmesniki zaradi samega projekta, temveč uvedbe ravno dovolj, da se ohrani razumljiva zasnova za ekipo.
YAGNI (Ne boste ga potrebovali) To je protistrup za pretirano načrtovanje. Pri projektih z zelo nestanovitnimi zahtevami je lahko poskus predvidevanja vseh možnih prihodnjih sprememb z dodajanjem plast za plastjo kontraproduktiven. Idealen pristop je najti ravnovesje: uporabiti SOLID za dele, za katere vemo, da se bodo razvijali ali spreminjali, in ohraniti preprostejše tisto, za kar vemo, da je stabilno.
Vsa ta načela skupaj vodijo do razvojnega sloga, kjer je prednostna naloga jasna, modularna in na sodelovanje usmerjena kodavendar brez zapadanja v fetišizem arhitekture zaradi arhitekture same.
Prednosti, omejitve in primeri, ko se biti purist ne splača
Načela SOLID veljajo za skoraj zlato pravilo pri srednje velikih in velikih projektihVendar to ne pomeni, da jih je treba dobesedno uporabljati v vsakem kontekstu. Pomembno je poznati tudi njihove omejitve.
V zelo majhnih aplikacijah, enkratnih skriptih ali nizkoproračunskih projektih, ki se ne bodo dolgoročno vzdrževali, je lahko uvedba preveč plasti abstrakcije problematična. dražje kot koristnoMiselni in kodirni stroški, povezani z uporabo DIP ali ISP za vse, se morda ne splačajo.
Obstajajo tudi situacije z zelo stroge časovne zahteve ali razvoj MVP-ja, kjer je hitrost dobave prednostna naloga. Smiselno je, da nekatere izboljšave SOLID preložimo na pozneje, ko se izdelek stabilizira in vemo, kateri deli bodo preživeli.
V starejših sistemih z veliko starejša kodaPoskus "utrjevanja" vsega naenkrat je običajno nerealen. V teh primerih je bolje, da načela postopoma uporabljamo na področjih kode, ki se pogosto uporabljajo ali ki blokirajo nove funkcije.
Končno obstajajo domene, ki so zelo občutljive na ekstremna zmogljivost (na primer nekatere visokofrekvenčne rutine), kjer lahko abstrakcijske plasti povzročijo neželene dodatne stroške. V teh primerih je včasih upravičeno žrtvovati nekaj čistosti SOLID-a v zelo specifičnih blokih, pod pogojem, da preostali del sistema ostane dobro zasnovan.
Na splošno vam razumevanje in uporaba načel SOLID v Pythonu omogoča prehod od kode, ki »preprosto deluje«, do kode, ki Dobro prestane preizkus časaBolje se prilagaja poslovnim spremembam, olajša sodelovanje v skupini in zmanjšuje presenečenja v proizvodnji; ključno je, da jih uporabljamo preudarno, saj vemo, kdaj se splača uvesti novo abstrakcijo in kdaj je bolje ohraniti preprostost in jasnost.
Strasten pisec o svetu bajtov in tehnologije nasploh. Rad delim svoje znanje s pisanjem in to je tisto, kar bom počel v tem blogu, saj vam bom pokazal vse najbolj zanimive stvari o pripomočkih, programski opremi, strojni opremi, tehnoloških trendih in še več. Moj cilj je, da vam pomagam krmariti po digitalnem svetu na preprost in zabaven način.
