Am văzut în episodul XVII cum putem grupa diferite informații în obiecte, dar modul în care erau create obiectele respective era puțin greoi. Am lucrat cu obiecte simple, dar este ușor să ne imaginăm că, pe măsură ce obiectele devin mai complexe, e destul de complicat să stabilim valori pentru fiecare proprietate în parte.
Constructori
Avem la dispoziție posibilitatea de a scrie funcții care creează obiectele noastre. De exemplu, pentru o persoană ale cărei proprietăți sunt numele și vârsta, am putea avea o funcție cu doi parametri (un string și un număr întreg) care să creeze obiectul respectiv.
Putem scrie o funcție simplă care să returneze un obiect:
1 2 3 4 5 6 |
func creeazaPersoana(unNume:String, oVarsta:Int) -> Persoana { var p = Persoana() p.nume = unNume p.varsta = oVarsta return p } |
Este o variantă bună, chiar foarte utilă în unele situații. Funcția ar putea fi folosită astfel:
1 |
var elev = creeazaPersoana("Dragoș", oVarsta:17) |
Apelul este puțin mai ciudat; începând cu al doilea argument, trebuie precizat și numele parametrului înaintea valorii.
Există și o alternativă: un constructor. Constructorii sunt funcții speciale care creează obiecte (de fapt, în Swift denumirea folosită este inițializator). Ele sunt definite în interiorul clasei. Spre deosebire de funcțiile obișnuite, constructorii nu au tip; va fi returnat obiectul creat, tipul fiind dat de clasa din care face parte constructorul.
De asemenea, numele constructorului este întotdeauna init. O clasă poate avea mai mulți constructori, cu parametri diferiți, atâta timp cât nu există pericolul să apară confuzii (vom vedea puțin mai încolo).
Să transformăm funcția noastră într-un constructor:
1 2 3 4 |
init(unNume:String, oVarsta:Int) { nume = unNume varsta = oVarsta } |
Observăm că nu apare o instrucțiune return; se va returna automat, la sfârșit, obiectul creat.
De asemenea, observăm că pentru proprietăți nu mai specificăm obiectul; nu mai este nevoie fiindcă se operează asupra obiectului care este creat.
Am spus că un constructor apare în interiorul clasei; întreaga noastră clasă ar arăta acum așa:
1 2 3 4 5 6 7 8 9 |
class Persoana { var nume : String var varsta : Int init(unNume:String, oVarsta:Int) { nume = unNume varsta = oVarsta } } |
Observăm că nu mai avem nevoie de valori implicite pentru proprietăți. Acestea vor primi valori în momentul creării obiectului cu ajutorul constructorului.
Pentru a utiliza constructorul, va trebui să îl apelăm în momentul creării obiectului. Crearea se va realiza acum folosind numele clasei și apoi, între paranteze, numele și valorile proprietăților. Un exemplu ar putea fi:
1 |
var elev = Persoana(unNume:"Dragoș", oVarsta:17) |
Poate v-ați dat seama că și în episodul anterior am apelat, de fapt, un constructor: unul fără parametri. Acesta există întotdeauna și nu face nimic special în afară de crearea obiectului; poartă denumirea de constructor implicit. Dacă dorim, putem să îl redefinim:
1 2 3 4 |
init() { nume = "Dragoș" varsta = 17 } |
Acum, toate obiectele create folosind apeluri de genul Persoana() vor crea obiecte pentru care numele este Dragoș și vârsta este 17.
Avem deja doi constructori; putem să mai adăugăm unul. De exemplu, poate dorim să creăm multe persoane care au 18 ani și am vrea să specificăm doar numele. Am putea avea constructorul:
1 2 3 4 |
init(unNume:String) { nume = unNume varsta = 18 } |
Acesta ar putea fi folosit astfel:
1 |
var elev = Persoana(unNume: "Antoniu") |
Dar, să presupunem că dorim să creăm acum multe persoane care au 16 ani. Am putea crede că putem crea un nou constructor:
1 2 3 4 |
init(unNume:String) { nume = unNume varsta = 18 } |
De fapt, nu se poate fiindcă acești ultimi doi constructori nu pot fi diferențiați în momentul apelului. Dacă ar exista amândoi, atunci un apel ca cel de mai sus (care creează o persoană cu numele Antoniu) nu ar ști exact pe care dintre ei să îl apeleze. Dar, dacă schimbăm numele parametrului, putem face distincția în momentul apelului.
1 2 3 4 |
init(altNume:String) { nume = altNume varsta = 16 } |
Putem crea și un constructor care să specifice doar vârsta:
1 2 3 4 |
init(oVarsta:Int) { nume = "Dana" varsta = oVarsta } |
Putem crea acum multe persoane cu numele Dana, specificând pentru fiecare doar vârsta; de exemplu:
1 |
var elev = Persoana(oVarsta:14) |
Avem deja patru constructori și îi putem folosi pe toți:
1 2 3 4 |
var p1 = Persoana() var p2 = Persoana(unNume:"Antoniu") var p3 = Persoana(oVarsta:14) var p4 = Persoana(unNume:"Sofia", oVarsta:15) |
Am creat o persoană cu numele Dragoș și vârsta de 17 ani, una cu numele Antoniu și vârsta de 18 ani, una cu numele Dana și vârsta de 14 ani și una cu numele Sofia și vârsta de 15 ani.
Nu trebuie neapărat să ne oprim aici; am mai putea avea un constructor în care să apară prima dată vârsta și apoi numele; nu are prea mult sens, dar e posibil. Nu va fi confundat cu cel în care precizăm numele și apoi vârsta fiindcă ordinea este diferită.
Obiecte în obiecte
Proprietățile obiectelor au tipuri. Ce fel de tipuri? În principiu, de orice fel, inclusiv diferite clase. Așadar, o proprietate a unui obiect poate fi, la rândul său, un obiect.
Să ne imaginăm că persoanele noastre au și o adresă. O adresă ar fi un obiect care să conțină informații cum ar fi strada, numărul, orașul etc. Pentru simplitate, ne vom limita la stradă și număr. Clasa noastră ar putea arăta astfel:
1 2 3 4 5 6 7 8 9 |
class Adresa { var strada : String var numar : String init(oStrada:String, unNumar:String) { strada = oStrada numar = unNumar } } |
La prima vedere ar putea părea ciudat că numărul este un string; dar, adresele pot avea numere ciudate cum ar fi 46B. Este important să ne gândim ce tip se potrivește cu informația pe care dorim să o păstrăm.
Acum, putem modifica clasa Persoana pentru ca ea să conțină și o adresă; vom avea doar un constructor care să ne permită să precizăm numele, vârsta și adresa.
1 2 3 4 5 6 7 8 9 10 11 |
class Persoana { var nume : String var varsta : Int var adresa : Adresa init(unNume:String, oVarsta:Int, oAdresa:Adresa) { nume = unNume varsta = oVarsta adresa = oAdresa } } |
Să vedem cum accesăm obiectele din obiecte. Nu e foarte complicat; trebuie doar să adaugăm puncte...
Mai exact, dacă elev este obiectul, atunci elev.adresa este obiectul din interior (o proprietate o primului obiect) și elev.adresa.strada, respectiv elev.adresa.numar, sunt proprietățile acestui obiect din interior.
Nu este nepărat nevoie să ne oprim aici. Putem avea obiecte în obiecte în obiecte în obiecte etc.
Să vedem acum un program complet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class Adresa { var strada : String var numar : String init(oStrada:String, unNumar:String) { strada = oStrada numar = unNumar } } class Persoana { var nume : String var varsta : Int var adresa : Adresa init(unNume:String, oVarsta:Int, oAdresa:Adresa) { nume = unNume varsta = oVarsta adresa = oAdresa } } var adresa = Adresa(oStrada:"Ferdinand I", unNumar:"47") var elev = Persoana(unNume:"Dragoș", oVarsta:17, oAdresa:adresa) print(elev.nume) print(elev.varsta) print(elev.adresa.strada) print(elev.adresa.numar) |
Observăm că am ales să nu punem clasa Adresa în interiorul clasei Persoana deși am fi putut. Așa, o putem utiliza independent în alte scopuri. Alternativa ar fi fost:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class Persoana { var nume : String var varsta : Int var adresa : Adresa init(unNume:String, oVarsta:Int, oAdresa:Adresa) { nume = unNume varsta = oVarsta adresa = oAdresa } class Adresa { var strada : String var numar : String init(oStrada:String, unNumar:String) { strada = oStrada numar = unNumar } } } var adresa = Persoana.Adresa(oStrada:"Ferdinand I", unNumar:"47") var elev = Persoana(unNume:"Dragoș", oVarsta:17, oAdresa:adresa) print(elev.nume) print(elev.varsta) print(elev.adresa.strada) print(elev.adresa.numar) |
Observați modificarea esențială din linia 23. Clasa Adresa nu mai există decât în interiorul clasei Persoana și nu mai poate fi utilizată independent.
Tot în linia 23 observăm că avem o variabilă pe care nu o folosim decât în linia 24; nu avem nevoie de ea. Putem scăpa de ea dacă ne gândim că un constructor returnează un obiect și în linia 24 avem nevoie de doar un astfel de obiect. Am putea rescrie liniile 23 și 24 astfel:
1 |
var elev = Persoana(unNume:"Dragoș", oVarsta:17, oAdresa:Adresa(oStrada:"Ferdinand I", unNumar:"47")) |
sau (dacă am ales varianta în care clasa Adresa este în interiorul clasei Persoana):
1 |
var elev = Persoana(unNume:"Dragoș", oVarsta:17, oAdresa:Persoana.Adresa(oStrada:"Ferdinand I", unNumar:"47")) |
E mai bine să definim clasele în interiorul altor clase sau nu? Depinde, dacă au sens să existe separat! În cazul nostru, probabil că e bine ca adresele să existe separat de persoane fiindcă s-ar putea să lucrăm și cu instituții, care au la rândul lor adrese și ar fi de dorit să nu trebuiască să avem o nouă clasă Adresa în interiorul clasei Institutie, care să fie la fel ca cea din interiorul clasei Persoana.