Principiile de bază ale POO. Principiile de bază ale POO și utilizarea lor

Nu știu să programez în limbaje orientate pe obiecte. nu am invatat. După 5 ani de programare industrială în Java, încă nu știu să creez un sistem bun în stil orientat pe obiecte. Doar că nu înțeleg.

Am încercat să învăț, sincer. Am studiat tipare, am citit codul proiectelor open source, am încercat să construiesc concepte coerente în capul meu, dar tot nu am înțeles principiile creării de programe de înaltă calitate orientate pe obiecte. Poate că altcineva le-a înțeles, dar nu eu.

Și iată câteva lucruri care mă fac confuz.

Nu știu ce este OOP

Serios. Îmi este greu să formulez ideile principale ale OOP. În programarea funcțională, una dintre ideile principale este apatridia. În structural - descompunere. În modul modular, funcționalitatea este împărțită în blocuri complete. În oricare dintre aceste paradigme, principiile dominante se aplică la 95% din cod, iar limbajul este conceput pentru a încuraja utilizarea lor. Nu cunosc astfel de reguli pentru OOP.
  • Abstracția
  • Încapsulare
  • Moştenire
  • Polimorfismul
Pare un set de reguli, nu-i așa? Deci acestea sunt regulile care trebuie respectate în 95% din cazuri? Hmm, hai să aruncăm o privire mai atentă.

Abstracția

Abstracția este un instrument de programare puternic. Acesta este ceea ce ne permite să construim sisteme mari și să menținem controlul asupra lor. Este puțin probabil că ne-am fi apropiat vreodată de nivelul actual de programe dacă nu am fi fost înarmați cu un astfel de instrument. Cu toate acestea, cum se leagă abstracția cu OOP?

În primul rând, abstracția nu este un atribut exclusiv al POO sau al programării în general. Procesul de creare a nivelurilor de abstractizare se extinde la aproape toate domeniile cunoașterii umane. Astfel, putem face judecăți despre materiale fără a intra în detaliile structurii lor moleculare. Sau vorbiți despre obiecte fără a menționa materialele din care sunt realizate. Sau vorbiți despre mecanisme complexe, cum ar fi un computer, o turbină de avion sau corpul uman, fără să vă amintiți detaliile individuale ale acestor entități.

În al doilea rând, au existat întotdeauna abstracții în programare, începând cu scrierile Adei Lovelace, care este considerată a fi prima programatoare din istorie. De atunci, oamenii au creat continuu abstracții în programele lor, adesea cu doar cele mai simple instrumente pentru aceasta. Astfel, Abelson și Sussman, în binecunoscuta lor carte, descriu cum să creeze un sistem de rezolvare a ecuațiilor care acceptă numere complexe și chiar polinoame, folosind numai proceduri și liste legate. Deci, ce mijloace suplimentare de abstractizare oferă OOP? Nu am nici o idee. Separarea codului în subrutine? Orice limbaj de nivel înalt poate face acest lucru. Combinarea rutinelor într-un singur loc? Există suficiente module pentru asta. Tipizare? A fost acolo cu mult înainte de OLP. Exemplul cu un sistem de rezolvare a ecuațiilor arată clar că construcția nivelurilor de abstractizare depinde nu atât de instrumentele de limbaj, cât de abilitățile programatorului.

Încapsulare

Principalul avantaj al încapsulării este ascunderea implementării. Codul client vede doar interfața și se poate baza doar pe ea. Acest lucru eliberează dezvoltatorii care pot decide să schimbe implementarea. Și asta e foarte tare. Dar întrebarea din nou este, ce legătură are OOP cu asta? Toate Paradigmele de mai sus implică ascunderea implementării. Când programați în C, alocați interfața în fișiere antet, Oberon vă permite să faceți câmpuri și metode locale pentru modul și, în sfârșit, abstracția în multe limbi este construită pur și simplu prin subrutine care încapsulează și implementarea. Mai mult decât atât, limbajele orientate pe obiecte însele sunt adesea încalcă regula de încapsulare, oferind acces la date prin metode speciale - getters și setters în Java, proprietăți în C# etc. (În comentarii am aflat că unele obiecte din limbaje de programare nu sunt obiecte din punct de vedere OOP: obiectele de transfer de date sunt responsabile exclusiv pentru transferul de date și, prin urmare, nu sunt entități OOP cu drepturi depline și, prin urmare, nu există trebuie să păstreze încapsularea. Pe de altă parte, este mai bine să păstrați metodele de acces pentru a menține flexibilitatea arhitecturii. În plus, unele limbaje orientate pe obiecte, cum ar fi Python, nu încearcă. să ascundeți orice, dar să vă bazați numai pe inteligența dezvoltatorilor care folosesc acest cod.

Moştenire

Moștenirea este unul dintre puținele lucruri noi care au intrat cu adevărat pe scenă datorită OOP. Nu, limbajele orientate pe obiect nu au creat o idee nouă - moștenirea poate fi implementată în orice altă paradigmă - dar OOP a adus pentru prima dată acest concept la nivelul limbajului în sine. Avantajele moștenirii sunt și ele evidente: când tu aproape mulțumit de o anumită clasă, puteți crea un descendent și puteți modifica o parte din funcționalitatea acestuia. În limbile care acceptă moștenirea multiplă, precum C++ sau Scala (în acesta din urmă, prin trăsături), apare un alt caz de utilizare - mixin-uri, clase mici care vă permit să „amesteci” funcționalitatea într-o nouă clasă fără a copia codul.

Deci, acesta este ceea ce diferențiază OOP ca paradigmă de ceilalți? Hmm... dacă da, de ce îl folosim atât de rar în cod real? Îți amintești ce am spus despre 95% din cod care se supune regulilor paradigmei dominante? Nu glumeam. În programarea funcțională, cel puțin 95% din cod utilizează date și funcții imuabile fără efecte secundare. În modul modular, aproape tot codul este logic împachetat în module. Susținătorii programării structurate, urmând preceptele lui Dijkstra, încearcă să despartă toate părțile programului în părți mici. Moștenirea este folosită mult mai rar. Poate în 10% din cod, poate în 50%, în unele cazuri (de exemplu, la moștenirea din clasele cadru) - în 70%, dar nu mai mult. Pentru că în majoritatea situațiilor este ușor nu este nevoie.

Mai mult, moștenirea periculos pentru un design bun. Atât de periculos încât Gang of Four (aparent predicatori ai OLP) în cartea lor recomandă înlocuirea acesteia cu delegarea ori de câte ori este posibil. Moștenirea așa cum există în limbile populare în prezent duce la un design fragil. După ce a fost moștenită de la un strămoș, o clasă nu mai poate fi moștenită de la alții. Schimbarea unui strămoș devine și ea periculoasă. Există, desigur, modificatori privați/protejați, dar aceștia necesită și abilități psihice considerabile pentru a ghici cum s-ar putea schimba clasa și cum o poate folosi codul clientului. Moștenirea este atât de periculoasă și incomodă încât cadrele mari (cum ar fi Spring și EJB în Java) o abandonează în favoarea altor instrumente, neorientate pe obiecte (de exemplu, metaprogramarea). Consecințele sunt atât de imprevizibile încât unele biblioteci (cum ar fi Guava) atribuie claselor lor modificatori care interzic moștenirea, iar în noua limbă Go s-a decis să se abandoneze complet ierarhia moștenirii.

Polimorfismul

Poate că polimorfismul este cel mai bun lucru despre programarea orientată pe obiecte. Datorită polimorfismului, la ieșire, un obiect de tip Persoană arată ca „Shandorkin Adam Impolitovici”, iar un obiect de tip Point arată ca „”. Acesta vă permite să scrieți „Mat1 * Mat2” și să obțineți produsul matricelor, similar cu produsul numerelor obișnuite. Fără el, nu ar fi posibil să citiți date din fluxul de intrare, fără să vă pese dacă provin din rețea, un fișier sau o linie din memorie. Oriunde există interfețe, este implicat și polimorfismul.

Îmi place foarte mult polimorfismul. Prin urmare, nici măcar nu voi vorbi despre problemele lui în limbile principale. De asemenea, voi păstra tăcerea în ceea ce privește îngustimea abordării de expediere numai după tip și despre cum s-ar putea face acest lucru. În majoritatea cazurilor funcționează așa cum ar trebui, ceea ce nu este rău. Întrebarea este: este polimorfismul chiar principiul care distinge OOP de alte paradigme? Dacă m-ai fi întrebat (și din moment ce citești acest text, poți presupune că ai întrebat), aș fi răspuns „nu”. Și motivul este încă același procent de utilizare în cod. Poate că interfețele și metodele polimorfe sunt puțin mai comune decât moștenirea. Dar comparați numărul de linii de cod pe care le ocupă cu numărul de linii scrise în stilul procedural obișnuit - există întotdeauna mai multe dintre acestea din urmă. Privind limbajele care încurajează acest stil de programare, nu le-aș numi polimorfe. Limbi care acceptă polimorfismul - da, este normal. Dar nu limbi polimorfe.

(Totuși, aceasta este părerea mea. Puteți întotdeauna să nu fiți de acord.)

Deci, abstracție, încapsulare, moștenire și polimorfism - toate acestea sunt în OOP, dar nimic din acestea nu este un atribut integral al acestuia. Atunci ce este OOP? Există o părere că esența programării orientate pe obiecte constă în obiecte (suna destul de logic) și clase. Este ideea de a combina codul și datele și ideea că obiectele dintr-un program reflectă entități din lumea reală. Vom reveni la această opinie mai târziu, dar mai întâi să punem câteva i.

Al cui OOP este mai tare?

Din partea anterioară este clar că limbajele de programare pot diferi foarte mult în modul în care implementează programarea orientată pe obiecte. Dacă luați totalitatea implementărilor OOP în toate limbile, atunci cel mai probabil nu veți găsi o singură caracteristică comună tuturor. Pentru a limita cumva această grădină zoologică și a clarifica raționamentul, mă voi concentra doar pe un singur grup - limbaje pur orientate pe obiecte, și anume Java și C#. Termenul „pur orientat pe obiecte” în acest caz înseamnă că limbajul nu acceptă alte paradigme sau le implementează prin același OOP. Python sau Ruby, de exemplu, nu vor fi pure, pentru că puteți scrie cu ușurință un program cu drepturi depline pe ele fără o singură declarație de clasă.

Pentru a înțelege mai bine esența OOP în Java și C#, să ne uităm la exemple de implementare a acestei paradigme în alte limbi.

Convorbire scurtă. Spre deosebire de omologii săi moderni, acest limbaj a fost tastat dinamic și a folosit un stil de transmitere a mesajelor pentru a implementa OOP. În loc să apeleze metode, obiectele și-au trimis mesaje unul altuia, iar dacă destinatarul nu a putut procesa ceea ce a venit, el a trimis pur și simplu mesajul către altcineva.

Lisp comun. Inițial, CL a urmat aceeași paradigmă. Apoi, dezvoltatorii au decis că scrierea `(send obj "some-message)` este prea lungă și au convertit notația într-un apel de metodă - `(some-method obj)` Astăzi, Common Lisp are un sistem matur de programare orientat pe obiecte ( CLOS) cu suport pentru moștenire multiplă, multimetode și metaclase O caracteristică distinctivă este că OOP în CL nu se învârte în jurul obiectelor, ci în jurul funcțiilor generice.

Clojure. Clojure are 2 sisteme de programare orientate pe obiecte - unul moștenit de la Java, iar al doilea, bazat pe multimetode și mai asemănător cu CLOS.

R. Acest limbaj pentru analiza datelor statistice are și 2 sisteme de programare orientate pe obiecte - S3 și S4. Ambele sunt moștenite din limbajul S (ceea ce nu este surprinzător, având în vedere că R este o implementare open source a S comercial). S4 se potrivește în mare măsură cu implementările OOP în limbile mainstream moderne. S3 este o opțiune mai ușoară, implementată pur și simplu folosind limbajul în sine: este creată o funcție generală care trimite cererile pe baza atributului „clasă” al obiectului primit.

JavaScript. Asemănător ideologic cu Smalltalk, deși folosește o sintaxă diferită. În loc de moștenire, folosește prototipul: dacă proprietatea dorită sau metoda apelată nu se află în obiectul în sine, atunci cererea este transmisă obiectului prototip (proprietatea prototip a tuturor obiectelor JavaScript). Un fapt interesant este că comportamentul tuturor obiectelor de clasă poate fi schimbat prin înlocuirea uneia dintre metodele prototip (de exemplu, adăugarea metodei `.toBASE64` pentru clasa string arată foarte bine).

Piton.În general, urmează același concept ca și limbajele mainstream, dar acceptă și trecerea căutării atributelor către alt obiect, ca în JavaScript sau Smalltalk.

Haskell.În Haskell nu există deloc stare și, prin urmare, nu există obiecte în sensul obișnuit. Totuși, există încă un fel de POO acolo: tipurile de date pot aparține uneia sau mai multor clase de tip. De exemplu, aproape toate tipurile din Haskell sunt în clasa Eq (responsabilă pentru operațiile de comparare între 2 obiecte), iar toate numerele sunt în plus în clasele Num (operații pe numere) și Ord (operații pe numere).<, <=, >=, >). În limbajele menstruale, tipurile corespund unor clase (de date), iar clasele de tip corespund interfețelor.

Cu stat sau apatrid?

Dar să revenim la sistemele de programare orientate pe obiecte mai comune. Ceea ce nu am putut să înțeleg niciodată este relația dintre obiecte și starea internă. Înainte de a studia OOP, totul era simplu și transparent: există structuri care stochează mai multe date conexe, există proceduri (funcții) care le procesează. plimbare (câine), retragere (cont, sumă). Apoi au sosit obiectele, și asta a fost și în regulă (deși citirea programelor a devenit mult mai dificilă - câinele meu mergea [cine?], iar contul retragea bani [de unde?]). Apoi am aflat despre ascunderea datelor. Mai puteam plimba câinele, dar nu mă mai puteam uita la compoziția hranei sale. Mâncarea nu a făcut nimic (probabil ați putea scrie food.eat(dog), dar eu tot prefer ca câinele meu să mănânce mâncare decât invers). Mâncarea este doar date, iar eu (și câinele meu) aveam nevoie doar să o accesez. Toate Doar. Dar nu a mai fost posibil să se încadreze în cadrul paradigmei, ca în blugi vechi de la sfârșitul anilor 90.

Bine, avem metode de acces la date. Să ne complacăm cu această mică înșelăciune și să ne prefacem că datele noastre sunt cu adevărat ascunse. Dar acum știu că obiectele sunt, în primul rând, date și apoi, poate, metode care le procesează. Am înțeles cum să scriu programe, spre ce să mă străduiesc atunci când proiectez.

Înainte să am timp să mă bucur de iluminare, am văzut cuvântul apatrid pe internet (aș jura că era înconjurat de strălucire și un halou atârna peste literele t și l). Un scurt studiu al literaturii de specialitate a dezvăluit lumea minunată a fluxului de control transparent și a firelor multiple simple, fără a fi nevoie să urmăriți consistența obiectului. Desigur, mi-am dorit imediat să ating această lume minunată. Cu toate acestea, aceasta a însemnat o respingere completă a oricăror reguli - acum nu era clar dacă câinele ar trebui să meargă singur sau dacă era nevoie de un manager special de plimbare pentru aceasta; aveți nevoie de un cont sau se va ocupa Banca de toată munca și, dacă da, ar trebui să anuleze banii static sau dinamic etc. Numărul de cazuri de utilizare a crescut exponențial și toate cazurile de utilizare viitoare ar putea duce la necesitatea unei refactorizări majore.

Încă nu știu când un obiect ar trebui să fie apatrid, când ar trebui să fie cu stare și când ar trebui să fie doar un container de date. Uneori este evident, dar de cele mai multe ori nu este.

Tastare: static sau dinamic?

Un alt lucru pe care nu îl pot decide despre limbaje precum C# și Java este dacă sunt tastate static sau dinamic. Majoritatea oamenilor vor exclama probabil: „Ce prostii! Tastat static, desigur! Tipurile sunt verificate în timpul compilării! Dar este chiar atât de simplu? Este adevărat că prin specificarea tipului X în parametrii unei metode, un programator poate fi sigur că obiectele de tip X îi vor fi întotdeauna transmise? Așa este - nu se poate, pentru că... va fi posibilă trecerea unui parametru de tip X la metoda X sau moștenitorul său. S-ar părea, și ce? Descendenții clasei X vor avea în continuare aceleași metode ca și X. Metodele sunt metode, dar logica de lucru se poate dovedi a fi complet diferită. Cel mai obișnuit caz este atunci când o clasă copil se dovedește a fi optimizată pentru alte nevoi decât X, iar metoda noastră se poate baza exact pe acea optimizare (dacă un astfel de scenariu vi se pare nerealist, încercați să scrieți un plugin pentru o bibliotecă open source dezvoltată - sau vei petrece câteva săptămâni pentru a analiza arhitectura și algoritmii bibliotecii, sau pur și simplu vei apela la întâmplare metode cu o semnătură potrivită). Drept urmare, programul funcționează, dar viteza de funcționare scade cu un ordin de mărime. Deși din punctul de vedere al compilatorului totul este corect. Este semnificativ faptul că Scala, care este numit un succesor al Java, în multe locuri permite implicit să fie transmise doar argumente de tipul specificat, deși acest comportament poate fi modificat.

O altă problemă este valoarea nulă, care poate fi transmisă în locul aproape oricărui obiect în Java și în locul oricărui obiect Nullable în C#. null aparține tuturor tipurilor deodată și, în același timp, nu aparține nimănui. null nu are nici câmpuri, nici metode, așa că orice apel la acesta (cu excepția verificării null) are ca rezultat o eroare. Se pare că toată lumea este obișnuită cu asta, dar pentru comparație, Haskell (și același Scala) este forțat să folosească tipuri speciale (Poate în Haskell, Opțiune în Scala) pentru a încheia funcții care în alte limbi ar putea returna nul. Drept urmare, ei spun adesea despre Haskell „este dificil să compilați un program pe el, dar dacă reușiți, atunci cel mai probabil funcționează corect”.

Pe de altă parte, limbajele principale nu sunt, evident, tipate dinamic și, prin urmare, nu au proprietăți precum simplitatea interfețelor și flexibilitatea procedurilor. Drept urmare, scrierea în stil Python sau Lisp devine și ea imposibilă.

Ce diferență are cum se numește această tastare dacă toate regulile sunt oricum cunoscute? Diferența este din ce parte abordezi designul arhitecturii. Există o dezbatere de lungă durată despre cum să construiți un sistem: faceți mai multe tipuri și puține funcții, sau puține tipuri și multe funcții? Prima abordare este utilizată în mod activ în Haskell, a doua în Lisp. Limbile moderne orientate pe obiecte folosesc ceva între ele. Nu vreau să spun că acest lucru este rău - probabil că are avantajele lui (la urma urmei, nu ar trebui să uităm că Java și C# sunt platforme multilingve), dar de fiecare dată când încep un nou proiect mă întreb de unde să încep proiectare – cu tipuri sau din funcționalitate.

Și mai departe...

Nu știu cum să modelez problema. Se crede că OOP vă permite să afișați obiecte din lumea reală într-un program. Totuși, în realitate am un câine (cu două urechi, patru labe și o zgarda) și un cont bancar (cu manager, funcționari și pauză de masă), iar în program - WalkManager, AccountFactory... ei bine, primești ideea. Și ideea nu este că programul are clase auxiliare care nu reflectă obiecte din lumea reală. Adevărul este că controlează modificările fluxului. Walking Manager îmi fură bucuria de a-mi plimba câinele și primesc bani dintr-un cont bancar fără suflet (hei, unde este fata aia drăguță de la care am schimbat banii săptămâna trecută?).

Poate că sunt un snob, dar am fost mult mai fericit când datele din computer erau doar date, chiar dacă descriau câinele meu sau un cont bancar. Aș putea face ceea ce era convenabil cu datele, fără a ține cont de lumea reală.

De asemenea, nu știu cum să descompun corect funcționalitatea. În Python sau C++, dacă aveam nevoie de o funcție mică pentru a converti un șir într-un număr, am scris-o doar la sfârșitul fișierului. În Java sau C# sunt forțat să-l pun într-o clasă separată StringUtils. În limbile pre-OO, aș putea declara un wrapper ad-hoc pentru a returna două valori dintr-o funcție (suma retrasă și soldul contului). În limbile OOP, va trebui să creez o clasă de rezultat al tranzacției cu drepturi depline. Și pentru o persoană nouă din proiect (sau chiar pentru mine o săptămână mai târziu), această clasă va părea la fel de importantă și fundamentală în arhitectura sistemului. 150 de fișiere și toate la fel de importante și fundamentale - oh da, arhitectură transparentă, niveluri minunate de abstractizare.

Nu știu să scriu programe eficiente. Programele eficiente folosesc puțină memorie - altfel colectorul de gunoi va încetini constant execuția. Dar pentru a efectua cea mai simplă operație în limbaje orientate pe obiecte, trebuie să creați o duzină de obiecte. Pentru a face o cerere HTTP, trebuie să creez un obiect de tip URL, apoi un obiect de tip HttpConnection, apoi un obiect de tip Request... ei bine, ați înțeles ideea. În programarea procedurală, aș numi pur și simplu mai multe proceduri, trecându-le o structură creată pe stivă. Cel mai probabil, un singur obiect ar fi creat în memorie - pentru a stoca rezultatul. În OOP, trebuie să aglomerez memoria tot timpul.

Poate că OOP este o paradigmă cu adevărat frumoasă și elegantă. Poate că nu sunt suficient de inteligent să înțeleg. Probabil că există cineva care poate crea un program foarte frumos într-un limbaj orientat pe obiecte. Ei bine, nu pot decât să-i invidiez.

Principii de bază și etapele orientate pe obiecte

programare

În teoria programării, OOP este definită ca o tehnologie pentru crearea unui software complex, care se bazează pe reprezentarea unui program ca o colecție de obiecte, fiecare dintre acestea fiind o instanță de un anumit tip (clasă), iar clasele formează o ierarhie cu

moștenirea proprietăților.

Interacțiunea obiectelor software într-un astfel de sistem se realizează prin transmiterea de mesaje.

Notă. Această reprezentare a programului a fost folosită pentru prima dată în limbajul de simulare pentru sisteme complexe Simula, care a apărut în anii 60.

Modul de reprezentare a unui program, care este firesc pentru limbaje de modelare, a fost dezvoltat într-un alt limbaj de modelare specializat - limbajul Smalltalk (anii 70), apoi a fost

Pagina 2 din 51

Principiile de bază ale POO

utilizat în versiuni noi ale limbajelor de programare universale, cum ar fi Pascal, C++,

Principalul avantaj al OOP- reducerea numărului de apeluri intermodule și reducerea cantității de informații transferate între module;

comparativ cu programarea modulară. Acest lucru se realizează prin localizarea și integrarea mai completă a datelor cu rutinele de procesare,

care permite dezvoltarea practic independentă a părților individuale

(obiectele) programului.

În plus, abordarea obiect oferă noi instrumente de dezvoltare tehnologică, cum ar fi moștenire, polimorfism, compoziție, conținut,

permițându-vă să construiți obiecte complexe din altele mai simple. Ca urmare, rata de reutilizare a codului crește semnificativ,

Devine posibil să se creeze biblioteci de obiecte pentru diverse aplicații, iar dezvoltatorilor li se oferă oportunități suplimentare de a crea sisteme de complexitate crescută.

Principalul dezavantaj al OOP este o ușoară scădere a performanței datorită unei organizări mai complexe a sistemului software.

OOP se bazează pe următoarele principii: abstractizare,

restricție de acces, modularitate, ierarhie, tastare, paralelism,

durabilitate.

Să ne uităm la ce este fiecare principiu.

A b p s t r a g e- procesul de izolare a abstracțiilor în domeniul unei probleme Abstracția este un set de caracteristici esențiale ale unui obiect care îl deosebesc de toate celelalte tipuri de obiecte.

astfel, ele definesc în mod clar trăsăturile unui obiect dat din punctul de vedere al examinării și analizei ulterioare. Conform definiției, abstracția folosită a unui obiect real depinde în mod semnificativ de problema rezolvată: într-un caz ne va interesa forma obiectului, în altul greutatea, în

al treilea - materialele din care este făcut, al patrulea - legea mișcării

Pagina 3 din 51

Principiile de bază ale POO

subiect, etc. Nivelul modern de abstractizare presupune unificarea tuturor proprietăților de abstractizare (atât cele referitoare la starea obiectului analizat,

şi determinarea comportamentului acestuia) într-o singură unitate de program un anumit

tip abstract (clasa).

Limitarea accesului- ascunderea elementelor individuale ale implementării abstracției care nu afectează caracteristicile esențiale ale acesteia în ansamblu.

Necesitatea de a restricționa accesul necesită o distincție între două părți în descrierea abstractizării:

interfață - un set de elemente de implementare a abstractizării accesibile din exterior (principalele caracteristici ale stării și comportamentului);

implementare - un set de elemente ale implementării unei abstracții care sunt inaccesibile din exterior (organizarea internă a abstracției și mecanismele de implementare a comportamentului acesteia).

Restricționarea accesului în POO permite dezvoltatorului:

realizează construcția sistemului în etape, fără a fi distras de caracteristicile de implementare ale abstracțiilor utilizate;

este ușor să modificați implementarea obiectelor individuale, care într-un sistem bine organizat nu vor necesita modificări ale altor obiecte.

Combinația de a combina toate proprietățile unui obiect (componentele stării și comportamentului său) într-o singură abstracție și restricționarea accesului la implementarea acestor proprietăți se numește încapsulare.

MODULARITATE- principiul dezvoltării unui sistem software,

sugerând implementarea sa sub formă de piese separate (module). La descompunerea unui sistem în module, este de dorit să se combine părți legate logic, asigurând, dacă este posibil, o reducere a numărului de conexiuni externe între module. Principiul este moștenit de la

Pagina 4 din 51

Principiile de bază ale POO

programarea modulară, urmând-o simplifică proiectarea și

depanarea programului.

Ierarhia este un sistem ordonat sau ordonat de abstracțiuni.

Principiul ierarhiei presupune utilizarea ierarhiilor în dezvoltarea sistemelor software.

OOP utilizează două tipuri de ierarhie.

Ierarhie „întreg/parte”- indică faptul că unele abstracții sunt activate

în abstracția în cauză ca părțile sale, de exemplu, o lampă este formată dintr-o bază, un filament incandescent și un bec. Această versiune a ierarhiei este utilizată în procesul de partiționare a sistemului în diferite etape de proiectare (la nivel logic - la descompunerea domeniului subiectului în obiecte, la nivel fizic - la descompunerea sistemului în module și la separarea proceselor individuale în un sistem multiproces).

Ierarhie „general/privat”- arată că o anumită abstracție este un caz special al unei alte abstracțiuni, de exemplu, „masa de sufragerie -

un anumit tip de masă” și „mesele sunt un anumit tip de mobilier”. Folosit pentru

dezvoltarea structurii claselor, atunci când clasele complexe sunt construite pe baza celor mai simple prin adăugarea lor de noi caracteristici și, eventual, clarificarea celor existente.

Unul dintre cele mai importante mecanisme ale POO este moștenirea proprietăților în ierarhia public/privat. Moștenirea este o relație între abstracții atunci când una dintre ele folosește o parte structurală sau funcțională a altei sau mai multe alte abstracțiuni (simple și, respectiv, multiple).

moştenire).

TIPZATION - o restricție impusă proprietăților obiectelor și

împiedicând interschimbabilitatea abstracțiilor de diferite tipuri (sau reducând foarte mult posibilitatea unei astfel de înlocuiri). În limbaje puternic tipizate, pentru fiecare obiect de program (variabilă, subrutină, parametru etc.)

declară un tip care definește un set de operații pe

Pagina 5 din 51

Principiile de bază ale POO

obiect software corespunzător. Limbajele de programare bazate pe Pascal discutate mai jos folosesc stricte, iar cele bazate pe C -

grad mediu de tipificare.

Utilizarea principiului de tastare asigură:

detectarea timpurie a erorilor asociate cu operațiuni nevalide asupra obiectelor program (erorile sunt detectate în etapa de compilare a programului la verificarea admisibilității efectuării unei operațiuni date pe un obiect program);

simplificarea documentației;

capacitatea de a genera cod mai eficient.

Un tip poate fi asociat cu un obiect software în mod static (tipul obiectului este determinat în timpul compilării - legarea timpurie)și dinamic (tipul obiectului este determinat numai în timpul execuției programului - legarea tardivă). Implementarea legăturii tardive într-un limbaj de programare vă permite să creați variabile - pointeri către obiecte aparținând unor clase diferite (obiecte polimorfe), care extinde semnificativ capacităţile limbii.

P a r l l e l i s m- proprietatea mai multor abstracții de a fi într-o stare activă în același timp, i.e. efectua unele operatii.

Există o serie de sarcini, a căror rezolvare necesită executarea simultană a anumitor secvențe de acțiuni. Pentru astfel de sarcini

de exemplu, sarcini de control automat al mai multor procese.

Paralelismul real se realizează numai la implementarea sarcinilor de acest tip pe sisteme multiprocesor, când este posibil să se execute fiecare proces pe un procesor separat. Sistemele cu un singur procesor simulează paralelismul prin împărțirea timpului procesorului între sarcinile de gestionare a diferitelor procese. În funcție de tipul de sistem de operare utilizat (un singur program sau multi-program)

Pagina 6 din 51

Principiile de bază ale POO

partajarea timpului poate fi realizată fie de sistemul în curs de dezvoltare (ca în

MS DOS) sau sistemul de operare utilizat (ca în sistemele Windows).

Stabilitate- proprietatea unei abstracții de a exista în timp indiferent de procesul care a generat un obiect software dat, și/sau în spațiu, deplasându-se din spațiul de adrese în care a fost creat.

Sunt:

∙ obiecte temporare care stochează rezultate intermediare ale anumitor acțiuni, cum ar fi calcule;

∙ obiecte locale care există în interiorul subrutinelor, a căror durată de viață este calculată de la apelul subrutinei până la finalizarea acestuia;

∙ obiecte globale care există în timp ce programul este încărcat în memorie;

∙ obiecte salvate, ale căror date sunt stocate în fișiere de memorie externă între sesiunile de program.

Toate principiile de mai sus sunt implementate într-o măsură sau alta în diferite versiuni ale limbajelor orientate pe obiecte.

Limbaje de programare orientate pe obiecte orientat pe obiecte, dacă implementează primele patru din cele șapte principii discutate.

Modelele de obiecte Delphi și C++Builder ocupă un loc special. Aceste modele generalizează experiența OOP pentru MS DOS și includ câteva caracteristici noi,

asigurând crearea eficientă a unor sisteme mai complexe. Pe baza acestor modele au fost create medii vizuale pentru dezvoltarea aplicațiilor Windows.

Complexitatea programării sub Windows a fost depășită semnificativ

redus prin crearea unor biblioteci speciale de obiecte care „ascundea” multe elemente ale tehnicilor de programare.

Pagina 7 din 51

Principiile de bază ale POO

Etapele dezvoltării sistemelor software folosind POO.

Procesul de dezvoltare software folosind POO include patru etape: analiză, proiectare, evoluție, modificare.

Să ne uităm la aceste etape.

Analiza . Scopul analizei este descrierea cea mai completă a problemei. În această etapă se realizează o analiză a domeniului problemei, se realizează o descompunere obiect a sistemului în curs de dezvoltare și se determină cele mai importante caracteristici ale comportamentului obiectelor (descrierea abstracțiilor). Pe baza rezultatelor analizei, este elaborată o diagramă bloc a produsului software, care prezintă principalele obiecte și mesaje transmise între ele și descrie și abstracțiile.

Design.

design logic,în care deciziile luate sunt practic independente de condițiile de funcționare (sistemul de operare și echipamentele utilizate);

design fizic,în care trebuie luaţi în considerare aceşti factori.

Design logic este de a dezvolta o structură de clasă:

sunt definite câmpuri pentru stocarea componentelor stării obiectelor și algoritmi ai metodelor care implementează aspecte ale comportamentului obiectelor. În acest caz, sunt utilizate tehnicile de dezvoltare a clasei discutate mai sus (moștenire,

compoziție, conținut, polimorfism etc.). Rezultatul este o ierarhie sau o diagramă de clase care arată relațiile dintre clase și o descriere a claselor.

Design fizic include combinarea descrierilor claselor în module, alegerea schemei de conectare a acestora (dispoziție statică sau dinamică), determinarea modalităților de interacțiune cu echipamentele, cu

sistem de operare și/sau alt software (de exemplu,

baze de date, programe de rețea), asigurarea sincronizării proceselor pentru sistemele de procesare paralelă etc.

Pagina 8 din 51

Principiile de bază ale POO

Evoluția sistemelor. Acesta este un proces de implementare pas cu pas și

conectarea claselor la proiect. Procesul începe cu crearea unui program de bază sau a unui proiect pentru un viitor produs software. Apoi, clasele sunt implementate și conectate astfel încât să creeze un aspect brut, dar, dacă este posibil,

un prototip funcțional al viitorului sistem. Este testat și depanat.

De exemplu, un astfel de prototip poate fi un sistem care include implementarea interfeței principale a unui produs software (mesajele nu sunt transmise către o parte a sistemului care nu este încă disponibilă). Drept urmare, primim un prototip funcțional al produsului, care, de exemplu, poate fi arătat clientului pentru a clarifica cerințele. Apoi, următorul grup de clase este conectat la sistem, de exemplu, asociat cu implementarea unui anumit element de meniu.

Versiunea rezultată este, de asemenea, testată și depanată și așa mai departe, până când toate capabilitățile sistemului sunt realizate.

Utilizarea unei implementări în etape simplifică foarte mult testarea și depanarea unui produs software.

Modificare. Este procesul de adăugare de noi funcționalități sau de modificare a proprietăților sistemului existente. De obicei,

modificările afectează implementarea clasei, lăsând interfața acesteia neschimbată, ceea ce atunci când utilizați OOP, de obicei, se întâmplă fără prea multe probleme, deoarece procesul de modificare afectează zona locală.

Schimbarea interfeței nu este, de asemenea, o sarcină foarte dificilă, dar soluția acesteia poate presupune necesitatea coordonării proceselor de interacțiune între obiecte, ceea ce va necesita modificări în alte clase ale programului. Cu toate acestea, reducerea numărului de parametri din interfață în comparație cu programarea modulară simplifică foarte mult acest proces.

Simplitatea modificării face relativ ușoară adaptarea sistemelor software la condițiile de operare în schimbare, ceea ce crește durata de viață a sistemelor, a căror dezvoltare necesită timp și resurse materiale enorme.

Pagina 9 din 51

Principiile de bază ale POO

O caracteristică specială a OOP este că un obiect sau un grup de obiecte poate fi dezvoltat separat și, prin urmare, proiectarea lor poate fi în etape diferite. De exemplu, clasele de interfață au fost deja implementate, dar structura claselor de domeniu este încă în curs de perfecționare.

De obicei, proiectarea începe atunci când orice fragment al domeniului subiectului este descris suficient de complet în procesul de analiză.

Vom începe analiza tehnicilor de bază ale abordării obiectelor cu descompunerea obiectului.

Descompunerea obiectului

După cum am menționat mai sus, atunci când utilizați tehnologia OOP, soluția este reprezentată sub formă rezultatul interacțiunii elementelor funcționale individuale un sistem care simulează procese,

care apar în domeniul subiectului sarcinii.

Într-un astfel de sistem, fiecare element funcțional, după ce a primit o influență de intrare (numită mesaj) în procesul de rezolvare a problemei,

efectuează acțiuni predeterminate (de exemplu, își poate schimba propria stare, poate efectua unele calcule, desenează o fereastră sau un grafic și, la rândul său, poate influența alte elemente). Procesul de rezolvare a problemelor este controlat de succesiune de mesaje. Trecând aceste mesaje de la element la element, sistemul efectuează acțiunile necesare.

Elemente funcționale ale sistemului, ale căror parametri și comportament sunt determinați de starea problemei și au un comportament independent

(adică, „capabil” să efectueze anumite acțiuni în funcție de mesajele primite și de starea elementului) sunt numite obiecte.

Procesul de reprezentare a domeniului problemei ca o colecție de obiecte care schimbă mesaje este numit descompunerea obiectului.

Pagina 10 din 51

Principiile de bază ale POO

Pentru a înțelege ce obiecte și mesaje sunt discutate atunci când se efectuează descompunerea obiectelor în fiecare caz specific, trebuie amintit că abordarea obiectului a fost propusă inițial pentru dezvoltarea modelelor de simulare a comportamentului sistemelor complexe. Setul de obiecte ale unor astfel de sisteme este de obicei determinat prin analiza proceselor simulate.

Exemplu. Descompunerea obiectului (model de simulare

benzinărie). Să fim interesați de dependența lungimii cozii la o benzinărie de numărul de benzinării, de parametrii de serviciu ai fiecărei benzinării și de intensitatea solicitărilor de realimentare (luăm în considerare același tip de combustibil).

Problemele de acest tip sunt de obicei rezolvate folosind modele de simulare. Modelul simulează programatic un proces real cu parametri dați, înregistrând simultan caracteristicile acestuia. Repetând procesul de simulare de multe ori cu valori diferite ale parametrilor de serviciu sau primirea cererilor, cercetătorul obține valori specifice ale caracteristicilor pe care sunt construite graficele dependențelor analizate.

Procesul de funcționare a unei benzinării cu trei stații de alimentare poate fi reprezentat sub forma unei diagrame.

Nu știu să programez în limbaje orientate pe obiecte. nu am invatat. După 5 ani de programare industrială în Java, încă nu știu să creez un sistem bun în stil orientat pe obiecte. Doar că nu înțeleg.

Am încercat să învăț, sincer. Am studiat tipare, am citit codul proiectelor open source, am încercat să construiesc concepte coerente în capul meu, dar tot nu am înțeles principiile creării de programe de înaltă calitate orientate pe obiecte. Poate că altcineva le-a înțeles, dar nu eu.

Și iată câteva lucruri care mă fac confuz.

Nu știu ce este OOP

Serios. Îmi este greu să formulez ideile principale ale OOP. În programarea funcțională, una dintre ideile principale este apatridia. În structural - descompunere. În modul modular, funcționalitatea este împărțită în blocuri complete. În oricare dintre aceste paradigme, principiile dominante se aplică la 95% din cod, iar limbajul este conceput pentru a încuraja utilizarea lor. Nu cunosc astfel de reguli pentru OOP.
  • Abstracția
  • Încapsulare
  • Moştenire
  • Polimorfismul
Pare un set de reguli, nu-i așa? Deci acestea sunt regulile care trebuie respectate în 95% din cazuri? Hmm, hai să aruncăm o privire mai atentă.

Abstracția

Abstracția este un instrument de programare puternic. Acesta este ceea ce ne permite să construim sisteme mari și să menținem controlul asupra lor. Este puțin probabil că ne-am fi apropiat vreodată de nivelul actual de programe dacă nu am fi fost înarmați cu un astfel de instrument. Cu toate acestea, cum se leagă abstracția cu OOP?

În primul rând, abstracția nu este un atribut exclusiv al POO sau al programării în general. Procesul de creare a nivelurilor de abstractizare se extinde la aproape toate domeniile cunoașterii umane. Astfel, putem face judecăți despre materiale fără a intra în detaliile structurii lor moleculare. Sau vorbiți despre obiecte fără a menționa materialele din care sunt realizate. Sau vorbiți despre mecanisme complexe, cum ar fi un computer, o turbină de avion sau corpul uman, fără să vă amintiți detaliile individuale ale acestor entități.

În al doilea rând, au existat întotdeauna abstracții în programare, începând cu scrierile Adei Lovelace, care este considerată a fi prima programatoare din istorie. De atunci, oamenii au creat continuu abstracții în programele lor, adesea cu doar cele mai simple instrumente pentru aceasta. Astfel, Abelson și Sussman, în binecunoscuta lor carte, descriu cum să creeze un sistem de rezolvare a ecuațiilor care acceptă numere complexe și chiar polinoame, folosind numai proceduri și liste legate. Deci, ce mijloace suplimentare de abstractizare oferă OOP? Nu am nici o idee. Separarea codului în subrutine? Orice limbaj de nivel înalt poate face acest lucru. Combinarea rutinelor într-un singur loc? Există suficiente module pentru asta. Tipizare? A fost acolo cu mult înainte de OLP. Exemplul cu un sistem de rezolvare a ecuațiilor arată clar că construcția nivelurilor de abstractizare depinde nu atât de instrumentele de limbaj, cât de abilitățile programatorului.

Încapsulare

Principalul avantaj al încapsulării este ascunderea implementării. Codul client vede doar interfața și se poate baza doar pe ea. Acest lucru eliberează dezvoltatorii care pot decide să schimbe implementarea. Și asta e foarte tare. Dar întrebarea din nou este, ce legătură are OOP cu asta? Toate Paradigmele de mai sus implică ascunderea implementării. Când programați în C, alocați interfața în fișiere antet, Oberon vă permite să faceți câmpuri și metode locale pentru modul și, în sfârșit, abstracția în multe limbi este construită pur și simplu prin subrutine care încapsulează și implementarea. Mai mult decât atât, limbajele orientate pe obiecte însele sunt adesea încalcă regula de încapsulare, oferind acces la date prin metode speciale - getters și setters în Java, proprietăți în C# etc. (În comentarii am aflat că unele obiecte din limbaje de programare nu sunt obiecte din punct de vedere OOP: obiectele de transfer de date sunt responsabile exclusiv pentru transferul de date și, prin urmare, nu sunt entități OOP cu drepturi depline și, prin urmare, nu există trebuie să păstreze încapsularea. Pe de altă parte, este mai bine să păstrați metodele de acces pentru a menține flexibilitatea arhitecturii. În plus, unele limbaje orientate pe obiecte, cum ar fi Python, nu încearcă. să ascundeți orice, dar să vă bazați numai pe inteligența dezvoltatorilor care folosesc acest cod.

Moştenire

Moștenirea este unul dintre puținele lucruri noi care au intrat cu adevărat pe scenă datorită OOP. Nu, limbajele orientate pe obiect nu au creat o idee nouă - moștenirea poate fi implementată în orice altă paradigmă - dar OOP a adus pentru prima dată acest concept la nivelul limbajului în sine. Avantajele moștenirii sunt și ele evidente: când tu aproape mulțumit de o anumită clasă, puteți crea un descendent și puteți modifica o parte din funcționalitatea acestuia. În limbile care acceptă moștenirea multiplă, precum C++ sau Scala (în acesta din urmă, prin trăsături), apare un alt caz de utilizare - mixin-uri, clase mici care vă permit să „amesteci” funcționalitatea într-o nouă clasă fără a copia codul.

Deci, acesta este ceea ce diferențiază OOP ca paradigmă de ceilalți? Hmm... dacă da, de ce îl folosim atât de rar în cod real? Îți amintești ce am spus despre 95% din cod care se supune regulilor paradigmei dominante? Nu glumeam. În programarea funcțională, cel puțin 95% din cod utilizează date și funcții imuabile fără efecte secundare. În modul modular, aproape tot codul este logic împachetat în module. Susținătorii programării structurate, urmând preceptele lui Dijkstra, încearcă să despartă toate părțile programului în părți mici. Moștenirea este folosită mult mai rar. Poate în 10% din cod, poate în 50%, în unele cazuri (de exemplu, la moștenirea din clasele cadru) - în 70%, dar nu mai mult. Pentru că în majoritatea situațiilor este ușor nu este nevoie.

Mai mult, moștenirea periculos pentru un design bun. Atât de periculos încât Gang of Four (aparent predicatori ai OLP) în cartea lor recomandă înlocuirea acesteia cu delegarea ori de câte ori este posibil. Moștenirea așa cum există în limbile populare în prezent duce la un design fragil. După ce a fost moștenită de la un strămoș, o clasă nu mai poate fi moștenită de la alții. Schimbarea unui strămoș devine și ea periculoasă. Există, desigur, modificatori privați/protejați, dar aceștia necesită și abilități psihice considerabile pentru a ghici cum s-ar putea schimba clasa și cum o poate folosi codul clientului. Moștenirea este atât de periculoasă și incomodă încât cadrele mari (cum ar fi Spring și EJB în Java) o abandonează în favoarea altor instrumente, neorientate pe obiecte (de exemplu, metaprogramarea). Consecințele sunt atât de imprevizibile încât unele biblioteci (cum ar fi Guava) atribuie claselor lor modificatori care interzic moștenirea, iar în noua limbă Go s-a decis să se abandoneze complet ierarhia moștenirii.

Polimorfismul

Poate că polimorfismul este cel mai bun lucru despre programarea orientată pe obiecte. Datorită polimorfismului, la ieșire, un obiect de tip Persoană arată ca „Shandorkin Adam Impolitovici”, iar un obiect de tip Point arată ca „”. Acesta vă permite să scrieți „Mat1 * Mat2” și să obțineți produsul matricelor, similar cu produsul numerelor obișnuite. Fără el, nu ar fi posibil să citiți date din fluxul de intrare, fără să vă pese dacă provin din rețea, un fișier sau o linie din memorie. Oriunde există interfețe, este implicat și polimorfismul.

Îmi place foarte mult polimorfismul. Prin urmare, nici măcar nu voi vorbi despre problemele lui în limbile principale. De asemenea, voi păstra tăcerea în ceea ce privește îngustimea abordării de expediere numai după tip și despre cum s-ar putea face acest lucru. În majoritatea cazurilor funcționează așa cum ar trebui, ceea ce nu este rău. Întrebarea este: este polimorfismul chiar principiul care distinge OOP de alte paradigme? Dacă m-ai fi întrebat (și din moment ce citești acest text, poți presupune că ai întrebat), aș fi răspuns „nu”. Și motivul este încă același procent de utilizare în cod. Poate că interfețele și metodele polimorfe sunt puțin mai comune decât moștenirea. Dar comparați numărul de linii de cod pe care le ocupă cu numărul de linii scrise în stilul procedural obișnuit - există întotdeauna mai multe dintre acestea din urmă. Privind limbajele care încurajează acest stil de programare, nu le-aș numi polimorfe. Limbi care acceptă polimorfismul - da, este normal. Dar nu limbi polimorfe.

(Totuși, aceasta este părerea mea. Puteți întotdeauna să nu fiți de acord.)

Deci, abstracție, încapsulare, moștenire și polimorfism - toate acestea sunt în OOP, dar nimic din acestea nu este un atribut integral al acestuia. Atunci ce este OOP? Există o părere că esența programării orientate pe obiecte constă în obiecte (suna destul de logic) și clase. Este ideea de a combina codul și datele și ideea că obiectele dintr-un program reflectă entități din lumea reală. Vom reveni la această opinie mai târziu, dar mai întâi să punem câteva i.

Al cui OOP este mai tare?

Din partea anterioară este clar că limbajele de programare pot diferi foarte mult în modul în care implementează programarea orientată pe obiecte. Dacă luați totalitatea implementărilor OOP în toate limbile, atunci cel mai probabil nu veți găsi o singură caracteristică comună tuturor. Pentru a limita cumva această grădină zoologică și a clarifica raționamentul, mă voi concentra doar pe un singur grup - limbaje pur orientate pe obiecte, și anume Java și C#. Termenul „pur orientat pe obiecte” în acest caz înseamnă că limbajul nu acceptă alte paradigme sau le implementează prin același OOP. Python sau Ruby, de exemplu, nu vor fi pure, pentru că puteți scrie cu ușurință un program cu drepturi depline pe ele fără o singură declarație de clasă.

Pentru a înțelege mai bine esența OOP în Java și C#, să ne uităm la exemple de implementare a acestei paradigme în alte limbi.

Convorbire scurtă. Spre deosebire de omologii săi moderni, acest limbaj a fost tastat dinamic și a folosit un stil de transmitere a mesajelor pentru a implementa OOP. În loc să apeleze metode, obiectele și-au trimis mesaje unul altuia, iar dacă destinatarul nu a putut procesa ceea ce a venit, el a trimis pur și simplu mesajul către altcineva.

Lisp comun. Inițial, CL a urmat aceeași paradigmă. Apoi, dezvoltatorii au decis că scrierea `(send obj "some-message)` este prea lungă și au convertit notația într-un apel de metodă - `(some-method obj)` Astăzi, Common Lisp are un sistem matur de programare orientat pe obiecte ( CLOS) cu suport pentru moștenire multiplă, multimetode și metaclase O caracteristică distinctivă este că OOP în CL nu se învârte în jurul obiectelor, ci în jurul funcțiilor generice.

Clojure. Clojure are 2 sisteme de programare orientate pe obiecte - unul moștenit de la Java, iar al doilea, bazat pe multimetode și mai asemănător cu CLOS.

R. Acest limbaj pentru analiza datelor statistice are și 2 sisteme de programare orientate pe obiecte - S3 și S4. Ambele sunt moștenite din limbajul S (ceea ce nu este surprinzător, având în vedere că R este o implementare open source a S comercial). S4 se potrivește în mare măsură cu implementările OOP în limbile mainstream moderne. S3 este o opțiune mai ușoară, implementată pur și simplu folosind limbajul în sine: este creată o funcție generală care trimite cererile pe baza atributului „clasă” al obiectului primit.

JavaScript. Asemănător ideologic cu Smalltalk, deși folosește o sintaxă diferită. În loc de moștenire, folosește prototipul: dacă proprietatea dorită sau metoda apelată nu se află în obiectul în sine, atunci cererea este transmisă obiectului prototip (proprietatea prototip a tuturor obiectelor JavaScript). Un fapt interesant este că comportamentul tuturor obiectelor de clasă poate fi schimbat prin înlocuirea uneia dintre metodele prototip (de exemplu, adăugarea metodei `.toBASE64` pentru clasa string arată foarte bine).

Piton.În general, urmează același concept ca și limbajele mainstream, dar acceptă și trecerea căutării atributelor către alt obiect, ca în JavaScript sau Smalltalk.

Haskell.În Haskell nu există deloc stare și, prin urmare, nu există obiecte în sensul obișnuit. Totuși, există încă un fel de POO acolo: tipurile de date pot aparține uneia sau mai multor clase de tip. De exemplu, aproape toate tipurile din Haskell sunt în clasa Eq (responsabilă pentru operațiile de comparare între 2 obiecte), iar toate numerele sunt în plus în clasele Num (operații pe numere) și Ord (operații pe numere).<, <=, >=, >). În limbajele menstruale, tipurile corespund unor clase (de date), iar clasele de tip corespund interfețelor.

Cu stat sau apatrid?

Dar să revenim la sistemele de programare orientate pe obiecte mai comune. Ceea ce nu am putut să înțeleg niciodată este relația dintre obiecte și starea internă. Înainte de a studia OOP, totul era simplu și transparent: există structuri care stochează mai multe date conexe, există proceduri (funcții) care le procesează. plimbare (câine), retragere (cont, sumă). Apoi au sosit obiectele, și asta a fost și în regulă (deși citirea programelor a devenit mult mai dificilă - câinele meu mergea [cine?], iar contul retragea bani [de unde?]). Apoi am aflat despre ascunderea datelor. Mai puteam plimba câinele, dar nu mă mai puteam uita la compoziția hranei sale. Mâncarea nu a făcut nimic (probabil ați putea scrie food.eat(dog), dar eu tot prefer ca câinele meu să mănânce mâncare decât invers). Mâncarea este doar date, iar eu (și câinele meu) aveam nevoie doar să o accesez. Toate Doar. Dar nu a mai fost posibil să se încadreze în cadrul paradigmei, ca în blugi vechi de la sfârșitul anilor 90.

Bine, avem metode de acces la date. Să ne complacăm cu această mică înșelăciune și să ne prefacem că datele noastre sunt cu adevărat ascunse. Dar acum știu că obiectele sunt, în primul rând, date și apoi, poate, metode care le procesează. Am înțeles cum să scriu programe, spre ce să mă străduiesc atunci când proiectez.

Înainte să am timp să mă bucur de iluminare, am văzut cuvântul apatrid pe internet (aș jura că era înconjurat de strălucire și un halou atârna peste literele t și l). Un scurt studiu al literaturii de specialitate a dezvăluit lumea minunată a fluxului de control transparent și a firelor multiple simple, fără a fi nevoie să urmăriți consistența obiectului. Desigur, mi-am dorit imediat să ating această lume minunată. Cu toate acestea, aceasta a însemnat o respingere completă a oricăror reguli - acum nu era clar dacă câinele ar trebui să meargă singur sau dacă era nevoie de un manager special de plimbare pentru aceasta; aveți nevoie de un cont sau se va ocupa Banca de toată munca și, dacă da, ar trebui să anuleze banii static sau dinamic etc. Numărul de cazuri de utilizare a crescut exponențial și toate cazurile de utilizare viitoare ar putea duce la necesitatea unei refactorizări majore.

Încă nu știu când un obiect ar trebui să fie apatrid, când ar trebui să fie cu stare și când ar trebui să fie doar un container de date. Uneori este evident, dar de cele mai multe ori nu este.

Tastare: static sau dinamic?

Un alt lucru pe care nu îl pot decide despre limbaje precum C# și Java este dacă sunt tastate static sau dinamic. Majoritatea oamenilor vor exclama probabil: „Ce prostii! Tastat static, desigur! Tipurile sunt verificate în timpul compilării! Dar este chiar atât de simplu? Este adevărat că prin specificarea tipului X în parametrii unei metode, un programator poate fi sigur că obiectele de tip X îi vor fi întotdeauna transmise? Așa este - nu se poate, pentru că... va fi posibilă trecerea unui parametru de tip X la metoda X sau moștenitorul său. S-ar părea, și ce? Descendenții clasei X vor avea în continuare aceleași metode ca și X. Metodele sunt metode, dar logica de lucru se poate dovedi a fi complet diferită. Cel mai obișnuit caz este atunci când o clasă copil se dovedește a fi optimizată pentru alte nevoi decât X, iar metoda noastră se poate baza exact pe acea optimizare (dacă un astfel de scenariu vi se pare nerealist, încercați să scrieți un plugin pentru o bibliotecă open source dezvoltată - sau vei petrece câteva săptămâni pentru a analiza arhitectura și algoritmii bibliotecii, sau pur și simplu vei apela la întâmplare metode cu o semnătură potrivită). Drept urmare, programul funcționează, dar viteza de funcționare scade cu un ordin de mărime. Deși din punctul de vedere al compilatorului totul este corect. Este semnificativ faptul că Scala, care este numit un succesor al Java, în multe locuri permite implicit să fie transmise doar argumente de tipul specificat, deși acest comportament poate fi modificat.

O altă problemă este valoarea nulă, care poate fi transmisă în locul aproape oricărui obiect în Java și în locul oricărui obiect Nullable în C#. null aparține tuturor tipurilor deodată și, în același timp, nu aparține nimănui. null nu are nici câmpuri, nici metode, așa că orice apel la acesta (cu excepția verificării null) are ca rezultat o eroare. Se pare că toată lumea este obișnuită cu asta, dar pentru comparație, Haskell (și același Scala) este forțat să folosească tipuri speciale (Poate în Haskell, Opțiune în Scala) pentru a încheia funcții care în alte limbi ar putea returna nul. Drept urmare, ei spun adesea despre Haskell „este dificil să compilați un program pe el, dar dacă reușiți, atunci cel mai probabil funcționează corect”.

Pe de altă parte, limbajele principale nu sunt, evident, tipate dinamic și, prin urmare, nu au proprietăți precum simplitatea interfețelor și flexibilitatea procedurilor. Drept urmare, scrierea în stil Python sau Lisp devine și ea imposibilă.

Ce diferență are cum se numește această tastare dacă toate regulile sunt oricum cunoscute? Diferența este din ce parte abordezi designul arhitecturii. Există o dezbatere de lungă durată despre cum să construiți un sistem: faceți mai multe tipuri și puține funcții, sau puține tipuri și multe funcții? Prima abordare este utilizată în mod activ în Haskell, a doua în Lisp. Limbile moderne orientate pe obiecte folosesc ceva între ele. Nu vreau să spun că acest lucru este rău - probabil că are avantajele lui (la urma urmei, nu ar trebui să uităm că Java și C# sunt platforme multilingve), dar de fiecare dată când încep un nou proiect mă întreb de unde să încep proiectare – cu tipuri sau din funcționalitate.

Și mai departe...

Nu știu cum să modelez problema. Se crede că OOP vă permite să afișați obiecte din lumea reală într-un program. Totuși, în realitate am un câine (cu două urechi, patru labe și o zgarda) și un cont bancar (cu manager, funcționari și pauză de masă), iar în program - WalkManager, AccountFactory... ei bine, primești ideea. Și ideea nu este că programul are clase auxiliare care nu reflectă obiecte din lumea reală. Adevărul este că controlează modificările fluxului. Walking Manager îmi fură bucuria de a-mi plimba câinele și primesc bani dintr-un cont bancar fără suflet (hei, unde este fata aia drăguță de la care am schimbat banii săptămâna trecută?).

Poate că sunt un snob, dar am fost mult mai fericit când datele din computer erau doar date, chiar dacă descriau câinele meu sau un cont bancar. Aș putea face ceea ce era convenabil cu datele, fără a ține cont de lumea reală.

De asemenea, nu știu cum să descompun corect funcționalitatea. În Python sau C++, dacă aveam nevoie de o funcție mică pentru a converti un șir într-un număr, am scris-o doar la sfârșitul fișierului. În Java sau C# sunt forțat să-l pun într-o clasă separată StringUtils. În limbile pre-OO, aș putea declara un wrapper ad-hoc pentru a returna două valori dintr-o funcție (suma retrasă și soldul contului). În limbile OOP, va trebui să creez o clasă de rezultat al tranzacției cu drepturi depline. Și pentru o persoană nouă din proiect (sau chiar pentru mine o săptămână mai târziu), această clasă va părea la fel de importantă și fundamentală în arhitectura sistemului. 150 de fișiere și toate la fel de importante și fundamentale - oh da, arhitectură transparentă, niveluri minunate de abstractizare.

Nu știu să scriu programe eficiente. Programele eficiente folosesc puțină memorie - altfel colectorul de gunoi va încetini constant execuția. Dar pentru a efectua cea mai simplă operație în limbaje orientate pe obiecte, trebuie să creați o duzină de obiecte. Pentru a face o cerere HTTP, trebuie să creez un obiect de tip URL, apoi un obiect de tip HttpConnection, apoi un obiect de tip Request... ei bine, ați înțeles ideea. În programarea procedurală, aș numi pur și simplu mai multe proceduri, trecându-le o structură creată pe stivă. Cel mai probabil, un singur obiect ar fi creat în memorie - pentru a stoca rezultatul. În OOP, trebuie să aglomerez memoria tot timpul.

Poate că OOP este o paradigmă cu adevărat frumoasă și elegantă. Poate că nu sunt suficient de inteligent să înțeleg. Probabil că există cineva care poate crea un program foarte frumos într-un limbaj orientat pe obiecte. Ei bine, nu pot decât să-i invidiez.

O idee generală despre programarea orientată pe obiecte și caracteristicile sale a fost discutată în prima lecție. Aici rezumăm materialul studiat în acest curs.

În Python, toate obiectele sunt derivate din clase și moștenesc atribute de la acestea. În acest caz, fiecare obiect își formează propriul spațiu de nume. Python acceptă caracteristici cheie de programare orientată pe obiecte, cum ar fi moștenirea, încapsularea și polimorfismul. Cu toate acestea, Python acceptă încapsularea în înțelegerea ascunderii datelor doar ca parte a convenției, nu a sintaxei limbajului.

Cursul nu a acordat atenție moștenirii multiple, când o clasă copil moștenește de la mai multe clase părinte. Această moștenire este pe deplin acceptată în Python și face posibilă combinarea atributelor a două sau mai multe clase într-o clasă derivată. În cazul moștenirii multiple, anumite caracteristici ale căutării de atribute trebuie luate în considerare.

Polimorfismul permite obiectelor din clase diferite să aibă interfețe similare. Este implementat prin declararea metodelor cu aceleași nume în ele. Metodele de supraîncărcare a operatorului pot fi, de asemenea, considerate o manifestare a polimorfismului ca o caracteristică a OOP.

Pe lângă moștenire, încapsulare și polimorfism, există și alte caracteristici ale OOP. Aceasta este compoziția sau agregarea, atunci când o clasă include apeluri către alte clase. Ca urmare, atunci când un obiect este creat dintr-o clasă agregată, sunt create obiecte din alte clase care sunt componente ale primei clase.

Clasele sunt de obicei plasate în module. Fiecare modul poate conține mai multe clase. La rândul lor, modulele pot fi combinate în pachete. Python folosește pachete pentru a organiza spațiile de nume.

Avantajele OOP

Caracteristicile programării orientate pe obiecte îi oferă o serie de avantaje.

Deci OOP vă permite să utilizați același cod de program cu date diferite. Pe baza claselor, sunt create multe obiecte, fiecare dintre acestea putând avea propriile valori de câmp. Nu este nevoie să introduceți multe variabile, deoarece obiectele au propriile lor spații de nume individuale. În acest sens, obiectele sunt similare structurilor de date. Un obiect poate fi gândit ca un fel de pachet de date la care sunt atașate instrumente pentru procesarea acestuia – metode.

Moștenirea vă permite să nu scrieți cod nou, ci să utilizați și să personalizați codul existent prin adăugarea și redefinirea atributelor.

Dezavantajele OOP

OOP vă permite să reduceți timpul de scriere a codului sursă, dar își asumă un rol mai mare în analiza preliminară a domeniului subiectului și a designului. Mult mai mult depinde de corectitudinea deciziilor în această etapă decât de scrierea efectivă a codului sursă.

Trebuie înțeles că aceeași problemă poate fi rezolvată prin diferite modele de obiecte, fiecare dintre acestea având propriile avantaje și dezavantaje. Doar un dezvoltator cu experiență poate spune care dintre ele va fi mai ușor de extins și de întreținut în viitor.

Caracteristicile OOP în Python

În comparație cu multe alte limbaje, programarea orientată pe obiecte în Python are o serie de caracteristici speciale.

Totul este un obiect - un număr, un șir, o listă, o funcție, o instanță a unei clase, clasa în sine, un modul. Deci o clasă este un obiect capabil să genereze alte obiecte - instanțe.

Nu există tipuri simple de date în Python. Toate tipurile sunt clase.

Încapsularea nu primește mult accent în Python. În alte limbaje de programare, de obicei nu puteți accesa direct o proprietate declarată într-o clasă. Poate fi furnizată o metodă specială pentru a-l schimba. În Python, accesul direct la proprietăți nu este considerat condamnabil.

Și, în sfârșit

Python este, până la urmă, un limbaj de scripting interpretat. Deși sunt scrise proiecte mari în el, este adesea folosit în dezvoltarea web și administrarea sistemului pentru a crea mici programe de script. În acest caz, instrumentele de limbaj încorporate sunt de obicei suficiente, nu este nevoie să vă „inventați” propriile clase.

(după cum înseamnă OOP) este, în primul rând, o paradigmă de programare.
Paradigma de programare definește modul în care programatorul vede execuția programului.
Astfel, paradigma OOP se caracterizează prin faptul că programatorul vede programul ca pe un set de obiecte care interacționează, în timp ce, de exemplu, în programarea funcțională programul este reprezentat ca o secvență de calcule de funcție. Programarea procedurală, sau, cum se mai spune corect, programarea operațională clasică, presupune scrierea unui algoritm pentru a rezolva o problemă; în acest caz, proprietățile așteptate ale rezultatului final nu sunt descrise sau indicate. Programarea structurată urmează, practic, aceleași principii ca și programarea procedurală, cu câteva tehnici utile adăugate.
Paradigmele de programare non-procedurale, care includ paradigma orientată pe obiecte, au idei complet diferite.
Definiția lui Gradi Bucha spune: „ Programare orientată pe obiecte este o metodologie de programare care se bazează pe reprezentarea unui program ca o colecție de obiecte, fiecare dintre acestea fiind o implementare a unei clase specifice (un tip special de tip), iar clasele formează o ierarhie bazată pe principiile ereditabilității.”
Programarea structurată și orientată pe obiecte se bazează pe metoda științifică cunoscută ca descompunere- o metodă care utilizează structura problemei și vă permite să împărțiți soluția unei probleme mari comune în rezolvarea unei secvențe de probleme mai mici. Descompunerea OOP apare nu în funcție de algoritmi, ci în funcție de obiectele utilizate în rezolvarea problemei. Această descompunere reduce dimensiunea sistemelor software prin reutilizarea mecanismelor comune. Se știe că sistemele de programare vizuală sau sistemele construite pe principiile programării orientate pe obiecte sunt mai flexibile și evoluează mai ușor în timp.

Istoria dezvoltării OOP isi are originea la sfarsitul anilor '60. Primul limbaj orientat pe obiecte a fost limbajul de programare Simula, creat la un centru de calculatoare din Norvegia. Limbajul a fost destinat să modeleze situații din lumea reală. O caracteristică specială a Simula a fost că programul scris în limbaj era organizat în obiecte de programare. Obiectele aveau instrucțiuni numite metode și date numite variabile; metodele și datele au determinat comportamentul obiectului. În timpul simulării, obiectul s-a comportat conform comportamentului său implicit și, dacă este necesar, a schimbat datele pentru a reflecta impactul acțiunii care i-a fost atribuită.

Astăzi există un număr suficient limbaje de programare orientate pe obiecte, dintre care cele mai populare sunt în prezent C++, Delphi, Java, Visual Basic, Flash. Dar, în plus, multe limbaje care sunt de obicei clasificate ca paradigmă procedurală au și proprietăți OOP, putând lucra cu obiecte. Astfel, programarea orientată pe obiecte în C este o secțiune mare de programare în acest limbaj, același lucru este valabil și pentru OOP în python și multe alte limbaje structurate.

Când vorbim despre POO, apare adesea o altă definiție - programare vizuală. În plus, oferă o utilizare extinsă a prototipurilor de obiecte, care sunt definite ca clase de obiecte.
Evenimente. Multe medii de programare vizuală implementează o caracteristică (pe lângă încapsulare, polimorfism și moștenire) a unui obiect - un eveniment. Evenimentele din programarea orientată pe obiecte sunt capacitatea de a procesa așa-numitele mesaje (sau evenimente) primite de la sistemul de operare Windows sau de la programul însuși. Acest principiu este tipic pentru toate componentele mediului care procesează diverse evenimente care apar în timpul execuției programului. În esență, un eveniment este o acțiune care activează o reacție standard a unui obiect. Un eveniment poate fi considerat, de exemplu, un clic pe un buton al mouse-ului, trecerea cursorului mouse-ului peste un element de meniu, deschiderea unei file etc. Ordinea în care sunt efectuate anumite acțiuni este determinată tocmai de evenimentele care au loc în sistem și de reacția obiectelor la acestea.
Clase și obiecte în POO- diverse concepte. Conceptul de clasă în OOP este un tip de date (la fel ca, de exemplu, Real sau String), iar un obiect este o instanță specifică a unei clase (copia sa), stocată în memoria computerului ca o variabilă de tipul corespunzător .
Clasă este un tip de date structurale. Clasa include o descriere a câmpurilor de date, precum și a procedurilor și funcțiilor care operează pe aceste câmpuri de date. Metoda POO- acestea sunt astfel de proceduri și funcții în raport cu clasele.
Clasele au câmpuri (cum ar fi tipul de date de înregistrare), proprietăți care sunt similare câmpurilor, dar au descriptori suplimentari care definesc mecanismele de scriere și citire a datelor și metode - subrutine care au ca scop modificarea câmpurilor și proprietăților clasei.

Principiile de bază ale POO

Principiile programării orientate pe obiecte, pe lângă gestionarea evenimentelor, sunt încapsularea, moștenirea, subclasarea și polimorfismul. Sunt utile și necesare în special atunci când se dezvoltă aplicații care sunt replicabile și ușor de întreținut.
Un obiect combină metode și proprietăți care nu pot exista separat de el. Prin urmare, dacă un obiect este șters, proprietățile sale și metodele asociate sunt șterse. La copiere, se întâmplă același lucru: obiectul este copiat ca întreg. Încapsulare OOP- aceasta este caracteristica descrisă.

Principiul și subclasele de moștenire OOP

Absolut toate obiectele sunt create pe baza claselor și moștenesc proprietățile și metodele acestor clase. La rândul lor, clasele pot fi create pe baza altor clase (părinți), atunci astfel de clase se numesc subclase (descendenți). Subclasele moștenesc toate proprietățile și metodele clasei părinte. În plus, pentru o subclasă sau o clasă descendentă, puteți defini proprietăți și metode noi, proprii, precum și modificați metodele clasei părinte. Modificările aduse proprietăților și metodelor unei clase părinte sunt urmărite în subclasele create pe baza acestei clase, precum și în obiectele create pe baza subclaselor. Despre aceasta este moștenirea OOP.

Polimorfismul OOP

În programarea orientată pe obiecte, polimorfismul este caracterizat ca interschimbabilitatea obiectelor cu aceeași interfață. Acest lucru poate fi explicat astfel: o clasă copil moștenește instanțe ale metodelor clasei părinte, dar execuția acestor metode poate avea loc într-un mod diferit, corespunzător specificului clasei copil, adică modificată.
Adică, dacă în programarea procedurală numele unei proceduri sau funcție identifică în mod unic codul executat legat de această procedură sau funcție, atunci în programarea orientată pe obiecte puteți folosi aceleași nume de metodă pentru a efectua diferite acțiuni. Adică rezultatul executării aceleiași metode depinde de tipul de obiect căruia i se aplică metoda.

Site-ul prezintă o teorie parțială a programării orientate pe obiecte pentru începători și exemple OOP de rezolvare a problemelor. Lecțiile OOP de pe site sunt algoritmi detaliați pentru finalizarea sarcinii. Pe baza finalizării acestor lucrări de laborator, studentul va fi capabil să rezolve în mod independent alte probleme similare în viitor.
Vă dorim o învățare ușoară și interesantă a programarii orientate pe obiecte!