Uvod v objektno usmerjeno programiranje
Izraz „OOP“ pomeni objektno usmerjeno programiranje, ki je način organiziranja in strukturiranja kode. OOP nam omogoča, da na program gledamo kot na zbirko predmetov, ki med seboj komunicirajo, in ne kot na zaporedje ukazov in funkcij.
V OOP je „objekt“ enota, ki vsebuje podatke in funkcije, ki delujejo na teh podatkih. Objekti so ustvarjeni na podlagi „razredov“, ki jih lahko razumemo kot načrte ali predloge za objekte. Ko imamo razred, lahko ustvarimo njegov „primerek“, ki je poseben objekt, narejen iz tega razreda.
Oglejmo si, kako lahko ustvarimo preprost razred v PHP. Pri definiranju razreda uporabimo ključno besedo „class“, ki ji sledi ime razreda, nato pa še oglati oklepaji, ki zapirajo funkcije razreda (imenovane „metode“) in spremenljivke razreda (imenovane „lastnosti“ ali „atributi“):
V tem primeru smo ustvarili razred z imenom Car
z eno funkcijo (ali „metodo“) z imenom
honk
.
Vsak razred mora rešiti le eno glavno nalogo. Če razred opravlja preveč stvari, ga je morda primerno razdeliti na manjše, specializirane razrede.
Razredi so običajno shranjeni v ločenih datotekah, da bi bila koda urejena in bi bilo po njej lažje krmariti. Ime datoteke
se mora ujemati z imenom razreda, tako da bi bilo ime datoteke za razred Car
Car.php
.
Pri poimenovanju razredov je dobro upoštevati konvencijo „PascalCase“, kar pomeni, da se vsaka beseda v imenu začne z veliko črko in ni podčrtank ali drugih ločil. Za metode in lastnosti velja konvencija „camelCase“, kar pomeni, da se začnejo z malo črko.
Nekatere metode v PHP imajo posebne vloge in imajo predpono __
(dva podčrtaja). Ena najpomembnejših posebnih
metod je „konstruktor“, ki je označen kot __construct
. Konstruktor je metoda, ki se samodejno pokliče pri
ustvarjanju novega primerka razreda.
Konstruktor pogosto uporabljamo za določanje začetnega stanja predmeta. Na primer, ko ustvarjate objekt, ki predstavlja osebo, lahko konstruktor uporabite za nastavitev njene starosti, imena ali drugih atributov.
Oglejmo si, kako uporabiti konstruktor v PHP:
V tem primeru ima razred Person
lastnost (spremenljivko) $age
in konstruktor, ki to lastnost
nastavi. Metoda howOldAreYou()
nato omogoča dostop do starosti osebe.
Psevdopremenljivka $this
se uporablja znotraj razreda za dostop do lastnosti in metod objekta.
Ključna beseda new
se uporablja za ustvarjanje novega primerka razreda. V zgornjem primeru smo ustvarili novo
osebo, staro 25 let.
Nastavite lahko tudi privzete vrednosti za parametre konstruktorja, če niso določeni pri ustvarjanju predmeta. Na primer:
Če pri ustvarjanju predmeta Person
ne določite starosti, bo uporabljena privzeta vrednost 20.
Lepo je, da lahko definicijo lastnosti z njeno inicializacijo prek konstruktorja skrajšamo in poenostavimo na naslednji način:
Za popolnost dodajmo, da imajo lahko objekti poleg konstruktorjev tudi destruktorje (metoda __destruct
), ki se
kličejo, preden se objekt sprosti iz pomnilnika.
Prostori imen
Prostori imen nam omogočajo organiziranje in združevanje sorodnih razredov, funkcij in konstant, pri čemer se izognemo konfliktom pri poimenovanju. Predstavljamo si jih lahko kot mape v računalniku, kjer vsaka mapa vsebuje datoteke, povezane z določenim projektom ali temo.
Prostori imen so še posebej uporabni v večjih projektih ali pri uporabi knjižnic tretjih oseb, kjer lahko pride do sporov v poimenovanju razredov.
Predstavljajte si, da imate v svojem projektu razred z imenom Car
in ga želite postaviti v imenski prostor
z imenom Transport
. To bi storili takole:
Če želite razred Car
uporabiti v drugi datoteki, morate navesti, iz katerega imenskega prostora razred
izhaja:
Za poenostavitev lahko na začetku datoteke navedete, kateri razred iz določenega imenskega prostora želite uporabiti, kar vam omogoča ustvarjanje primerkov brez navajanja celotne poti:
Dedovanje
Dedovanje je orodje objektno usmerjenega programiranja, ki omogoča ustvarjanje novih razredov na podlagi obstoječih, dedovanje njihovih lastnosti in metod ter njihovo razširitev ali redefiniranje po potrebi. Dedovanje zagotavlja ponovno uporabnost kode in hierarhijo razredov.
Preprosto povedano, če imamo en razred in želimo ustvariti drugega, ki izhaja iz njega, vendar z nekaterimi spremembami, lahko novi razred „podedujemo“ od prvotnega.
V jeziku PHP se dedovanje izvaja s ključno besedo extends
.
Naš razred Person
shranjuje informacije o starosti. Imamo lahko še en razred, Student
, ki
razširja Person
in dodaja informacije o področju študija.
Oglejmo si primer:
Kako deluje ta koda?
- S ključno besedo
extends
smo razširili razredPerson
, kar pomeni, da razredStudent
podeduje vse metode in lastnosti od razredaPerson
. - Ključna beseda
parent::
nam omogoča klicanje metod iz nadrejenega razreda. V tem primeru smo poklicali konstruktor iz razredaPerson
, preden smo v razredStudent
dodali svojo funkcionalnost. In podobno tudi metodo razredaprintInformation()
prednika, preden smo izpisali podatke o učencu.
Dedovanje je namenjeno situacijam, v katerih med razredi obstaja razmerje „is a“. Na primer, razred Student
je razred Person
. Mačka je žival. Omogoča nam, da v primerih, ko v kodi pričakujemo en objekt (npr.
„Oseba“), namesto njega uporabimo izpeljani objekt (npr. „Študent“).
Bistveno se je zavedati, da glavni namen dedovanja ni preprečevanje podvajanja kode. Ravno nasprotno, napačna uporaba dedovanja lahko privede do zapletene in težko vzdrževane kode. Če med razredi ni razmerja „je a“, moramo namesto dedovanja razmisliti o kompoziciji.
Upoštevajte, da metode printInformation()
v razredih Person
in Student
prikazujejo
nekoliko drugačne informacije. Dodamo lahko tudi druge razrede (na primer Employee
), ki bodo zagotovili druge
izvedbe te metode. Sposobnost objektov različnih razredov, da se na isto metodo odzivajo na različne načine, se imenuje
polimorfizem:
Sestavljanje
Kompozicija je tehnika, pri kateri namesto dedovanja lastnosti in metod iz drugega razreda preprosto uporabimo njegov primerek v svojem razredu. Tako lahko združimo funkcionalnosti in lastnosti več razredov, ne da bi ustvarjali zapletene strukture dedovanja.
Na primer, imamo razred Engine
in razred Car
. Namesto da bi rekli „Avto je motor“, rečemo
„Avto ima motor“, kar je tipično kompozicijsko razmerje.
V tem primeru razred Car
nima vseh lastnosti in metod razreda Engine
, vendar ima do njih dostop prek
lastnosti razreda $engine
.
Prednost sestave je večja prilagodljivost načrtovanja in boljša prilagodljivost za prihodnje spremembe.
Vidnost
V jeziku PHP lahko za lastnosti, metode in konstante razreda določite vidnost. Vidnost določa, kje lahko dostopate do teh elementov.
- Public: Če je element označen kot
public
, to pomeni, da lahko do njega dostopate od koder koli, tudi zunaj razreda. - Zaščiten: Element, označen kot
protected
, je dostopen le znotraj razreda in vseh njegovih potomcev (razredov, ki dedujejo od njega). - Zasebno: Če je element označen kot
private
, lahko do njega dostopate le znotraj razreda, v katerem je bil opredeljen.
Če ne določite vidnosti, PHP samodejno nastavi vidnost na public
.
Oglejmo si vzorec kode:
Nadaljujemo z dedovanjem razredov:
V tem primeru lahko metoda printProperties()
v razredu ChildClass
dostopa do javnih in zaščitenih
lastnosti, ne more pa dostopati do zasebnih lastnosti nadrejenega razreda.
Podatki in metode morajo biti čim bolj skriti in dostopni le prek določenega vmesnika. Tako lahko spremenite notranjo implementacijo razreda, ne da bi to vplivalo na preostalo kodo.
Končna ključna beseda
V jeziku PHP lahko ključno besedo final
uporabimo, če želimo preprečiti, da bi razred, metodo ali konstanto
podedovali ali prepisali. Če je razred označen kot final
, ga ni mogoče razširiti. Če je metoda označena kot
final
, je ni mogoče prepisati v podrazredu.
Če se zavedamo, da določenega razreda ali metode ne bomo več spreminjali, lahko lažje izvajamo spremembe, ne da bi nas skrbelo za morebitne konflikte. Tako lahko na primer dodamo novo metodo, ne da bi se bali, da ima potomec že metodo z istim imenom, kar bi povzročilo kolizijo. Ali pa lahko spremenimo parametre metode, spet brez tveganja, da bi povzročili neskladje s prepisano metodo v potomcu.
V tem primeru bo poskus dedovanja iz končnega razreda FinalClass
povzročil napako.
Statične lastnosti in metode
Ko v jeziku PHP govorimo o „statičnih“ elementih razreda, imamo v mislih metode in lastnosti, ki pripadajo samemu razredu in ne določenemu primerku razreda. To pomeni, da vam za dostop do njih ni treba ustvariti primerka razreda. Namesto tega jih pokličete ali do njih dostopate neposredno prek imena razreda.
Upoštevajte, da ker statični elementi pripadajo razredu in ne njegovim instancam, znotraj statičnih metod ne morete
uporabljati psevdo-premenljivke $this
.
Uporaba statičnih lastnosti vodi v obskurno kodo, polno pasti, zato jih nikoli ne smete uporabljati in tu ne bomo prikazali primera. Po drugi strani pa so statične metode uporabne. Tukaj je primer:
V tem primeru smo ustvarili razred Calculator
z dvema statičnima metodama. Ti metodi lahko pokličemo
neposredno, ne da bi ustvarili primerek razreda z uporabo operatorja ::
. Statične metode so še posebej uporabne za
operacije, ki niso odvisne od stanja določenega primerka razreda.
Konstante razreda
Znotraj razredov lahko določimo konstante. Konstante so vrednosti, ki se med izvajanjem programa nikoli ne spremenijo. V nasprotju s spremenljivkami ostane vrednost konstante enaka.
V tem primeru imamo razred Car
s konstanto NumberOfWheels
. Pri dostopu do konstante znotraj razreda
lahko namesto imena razreda uporabimo ključno besedo self
.
Objektni vmesniki
Objektni vmesniki delujejo kot „pogodbe“ za razrede. Če naj razred implementira objektni vmesnik, mora vsebovati vse metode, ki jih vmesnik opredeljuje. To je odličen način za zagotavljanje, da se določeni razredi držijo iste „pogodbe“ ali strukture.
V PHP so vmesniki opredeljeni s ključno besedo interface
. Vse metode, opredeljene v vmesniku, so javne
(public
). Ko razred implementira vmesnik, uporabi ključno besedo implements
.
Če razred implementira vmesnik, vendar niso definirane vse pričakovane metode, PHP vrže napako.
Razred lahko implementira več vmesnikov hkrati, kar se razlikuje od dedovanja, pri katerem lahko razred deduje samo od enega razreda:
Abstraktni razredi
Abstraktni razredi služijo kot osnovne predloge za druge razrede, vendar njihovih primerkov ne morete ustvariti neposredno. Vsebujejo mešanico popolnih metod in abstraktnih metod, ki nimajo določene vsebine. Razredi, ki dedujejo od abstraktnih razredov, morajo zagotoviti definicije za vse abstraktne metode iz starševskega razreda.
Za opredelitev abstraktnega razreda uporabimo ključno besedo abstract
.
V tem primeru imamo abstraktni razred z eno redno in eno abstraktno metodo. Nato imamo razred Child
, ki podeduje
od AbstractClass
in zagotavlja implementacijo abstraktne metode.
V čem se vmesniki in abstraktni razredi razlikujejo? Abstraktni razredi lahko vsebujejo tako abstraktne kot konkretne metode, medtem ko vmesniki le določajo, katere metode mora razred implementirati, ne zagotavljajo pa implementacije. Razred lahko podeduje le en abstraktni razred, implementira pa lahko poljubno število vmesnikov.
Preverjanje tipa
Pri programiranju je ključnega pomena, da zagotovimo, da so podatki, s katerimi delamo, pravilnega tipa. V jeziku PHP imamo na voljo orodja, ki to zagotavljajo. Preverjanje, ali so podatki pravilnega tipa, se imenuje „preverjanje tipa“.
Tipi, ki jih lahko srečamo v PHP:
- Osnovne vrste: To so
int
(cela števila),float
(števila s plavajočo vejico),bool
(logične vrednosti),string
(nizi),array
(polja) innull
. - Tredi: Kadar želimo, da je vrednost primerek določenega razreda.
- Vmesniki: Opredeljuje nabor metod, ki jih mora razred implementirati. Vrednost, ki ustreza vmesniku, mora imeti te metode.
- Mešani tipi: Določimo lahko, da ima lahko spremenljivka več dovoljenih tipov.
- Void: Ta posebni tip označuje, da funkcija ali metoda ne vrača nobene vrednosti.
Oglejmo si, kako spremeniti kodo, da bo vključevala tipe:
Na ta način zagotovimo, da naša koda pričakuje in dela s podatki pravilne vrste, kar nam pomaga preprečiti morebitne napake.
Nekaterih vrst ni mogoče neposredno zapisati v PHP. V tem primeru so navedeni v komentarju phpDoc, ki je standardna oblika
za dokumentiranje kode PHP in se začne s /**
in konča s */
. Omogoča dodajanje opisov razredov, metod
itd. Prav tako pa tudi za navajanje kompleksnih tipov s tako imenovanimi opombami @var
, @param
in
@return
. Te vrste nato uporabljajo orodja za statično analizo kode, vendar jih PHP sam ne preverja.
Primerjava in identiteta
V PHP lahko predmete primerjate na dva načina:
- Primerjava vrednosti
==
: Preveri, ali sta objekta istega razreda in imata v svojih lastnostih enake vrednosti. - Primerjava identitete
===
: Preveri, ali gre za isti primerek objekta.
Upravljavec spletne strani instanceof
Operator instanceof
omogoča ugotavljanje, ali je dani predmet primerek določenega razreda, potomec tega razreda
ali pa implementira določen vmesnik.
Predstavljajte si, da imamo razred Person
in še en razred Student
, ki je potomec razreda
Person
:
Iz izpisov je razvidno, da objekt $student
velja za primerek obeh razredov Student
in
Person
.
Fluentni vmesniki
„Fluentni vmesnik“ je tehnika v OOP, ki omogoča veriženje metod v enem samem klicu. To pogosto poenostavi in razjasni kodo.
Ključni element tekočega vmesnika je, da vsaka metoda v verigi vrne referenco na trenutni objekt. To dosežemo z uporabo
return $this;
na koncu metode. Ta slog programiranja je pogosto povezan z metodami, imenovanimi „nastavljalci“,
ki nastavljajo vrednosti lastnosti objekta.
Oglejmo si, kako bi lahko bil videti tekoči vmesnik za pošiljanje e-pošte:
V tem primeru so metode setFrom()
, setRecipient()
in setMessage()
uporabljene za
nastavitev ustreznih vrednosti (pošiljatelj, prejemnik, vsebina sporočila). Po nastavitvi vsake od teh vrednosti metode vrnejo
trenutni objekt ($email
), kar nam omogoča, da za njo verižno priključimo drugo metodo. Na koncu pokličemo metodo
send()
, ki dejansko pošlje elektronsko sporočilo.
Zahvaljujoč tekočim vmesnikom lahko pišemo kodo, ki je intuitivna in lahko berljiva.
Kopiranje s clone
V jeziku PHP lahko ustvarimo kopijo predmeta z uporabo operatorja clone
. Tako dobimo nov primerek z enako
vsebino.
Če moramo pri kopiranju objekta spremeniti nekatere njegove lastnosti, lahko v razredu definiramo posebno metodo
__clone()
. Ta metoda se samodejno pokliče, ko je objekt kloniran.
V tem primeru imamo razred Sheep
z eno lastnostjo $name
. Ko kloniramo primerek tega razreda, metoda
__clone()
poskrbi, da ime klonirane ovce dobi predpono „Clone of“.
Lastnosti
Značilnosti v PHP so orodje, ki omogoča souporabo metod, lastnosti in konstant med razredi ter preprečuje podvajanje kode. Lahko si jih predstavljate kot mehanizem „kopiraj in prilepi“ (Ctrl-C in Ctrl-V), pri katerem se vsebina lastnosti „prilepi“ v razrede. To vam omogoča ponovno uporabo kode, ne da bi morali ustvarjati zapletene hierarhije razredov.
Oglejmo si preprost primer uporabe lastnosti v jeziku PHP:
V tem primeru imamo lastnost z imenom Honking
, ki vsebuje eno metodo honk()
. Nato imamo dva
razreda: Car
in Truck
, ki oba uporabljata lastnost Honking
. Posledično oba razreda
„imata“ metodo honk()
in jo lahko kličemo na predmetih obeh razredov.
Značilnosti omogočajo enostavno in učinkovito izmenjavo kode med razredi. Ne vstopajo v hierarhijo dedovanja, tj.
$car instanceof Honking
bo vrnil false
.
Izjeme
Izjeme v OOP nam omogočajo elegantno obravnavanje napak in nepričakovanih situacij v naši kodi. So predmeti, ki nosijo informacije o napaki ali nenavadni situaciji.
V jeziku PHP imamo vgrajen razred Exception
, ki služi kot osnova za vse izjeme. Ta ima več metod, s katerimi
lahko pridobimo več informacij o izjemi, kot so sporočilo o napaki, datoteka in vrstica, v kateri se je napaka
pojavila, itd.
Ko se v kodi pojavi napaka, lahko izjemo „vržemo“ z uporabo ključne besede throw
.
Ko funkcija division()
kot drugi argument prejme ničlo, vrže izjemo s sporočilom o napaki
'Division by zero!'
. Da bi preprečili sesutje programa, ko se vrže izjema, jo ujamemo v blok
try/catch
:
Koda, ki lahko vrže izjemo, je zavita v blok try
. Če je izjema vržena, se izvajanje kode premakne v blok
catch
, kjer lahko obravnavamo izjemo (npr. napišemo sporočilo o napaki).
Za blokoma try
in catch
lahko dodamo neobvezni blok finally
, ki se vedno izvede ne glede
na to, ali je bila izjema vržena ali ne (tudi če v bloku try
ali catch
uporabimo return
,
break
ali continue
):
Ustvarimo lahko tudi lastne razrede izjem (hierarhijo), ki dedujejo po razredu Exception. Kot primer si oglejmo preprosto bančno aplikacijo, ki omogoča vplačila in izplačila:
Če pričakujete različne vrste izjem, lahko za en sam blok try
določite več blokov catch
.
V tem primeru je pomembno upoštevati vrstni red blokov catch
. Ker vse izjeme dedujejo od
BankingException
, bi se, če bi imeli ta blok prvi, vse izjeme ujele v njem, ne da bi koda dosegla naslednje bloke
catch
. Zato je pomembno, da so bolj specifične izjeme (tj. tiste, ki dedujejo od drugih) višje v vrstnem redu
blokov catch
kot njihove nadrejene izjeme.
Iteracije
V jeziku PHP lahko z zanko foreach
krožite po predmetih, podobno kot krožite po polju. Da bi to delovalo, mora
objekt implementirati poseben vmesnik.
Prva možnost je implementacija vmesnika Iterator
, ki ima metode current()
, ki vrača trenutno
vrednost, key()
, ki vrača ključ, next()
, ki se premakne na naslednjo vrednost, rewind()
,
ki se premakne na začetek, in valid()
, ki preveri, ali smo že na koncu.
Druga možnost je, da implementiramo vmesnik IteratorAggregate
, ki ima samo eno metodo getIterator()
.
Ta bodisi vrne nadomestni objekt, ki bo zagotovil prehod, bodisi je lahko generator, ki je posebna funkcija, ki uporablja
yield
za zaporedno vračanje ključev in vrednosti:
Najboljše prakse
Ko obvladate osnovna načela objektno usmerjenega programiranja, se je treba osredotočiti na najboljše prakse v OOP. Te vam bodo pomagale pri pisanju kode, ki ni le funkcionalna, temveč tudi berljiva, razumljiva in jo je mogoče zlahka vzdrževati.
- Oddelitev interesov: Vsak razred mora imeti jasno opredeljeno odgovornost in mora obravnavati le eno primarno nalogo. Če razred počne preveč stvari, je morda primerno, da ga razdelite na manjše, specializirane razrede.
- Enkapsulacija: Podatki in metode morajo biti čim bolj skriti in dostopni le prek opredeljenega vmesnika. To omogoča spreminjanje notranje implementacije razreda, ne da bi to vplivalo na preostalo kodo.
- Vključevanje odvisnosti: Namesto da bi odvisnosti ustvarjali neposredno v razredu, bi jih morali „vbrizgati“ od zunaj. Za globlje razumevanje tega načela priporočamo poglavja o vbrizgavanju odvisnosti.