NEW: Join my free 100 Days of SwiftUI challenge today! >>

Polimorfismul și typecasting-ul

Deoarece clasele pot moșteni de la alte clase (de ex. CountrySinger poate moșteni de la Singer) înseamnă că o clasă este un superset al alteia: clasa B are toate lucrurile pe care A le are, cu ceva în plus. Ceea ce înseamnă că poți trata B drept tip B sau drept tip A, în funcție de nevoie tale.

Confuz? Să încercăm niște cod:

class Album {
    var name: String

    init(name: String) {
        self.name = name
    }
}

class StudioAlbum: Album {
    var studio: String

    init(name: String, studio: String) {
        self.studio = studio
        super.init(name: name)
    }
}

class LiveAlbum: Album {
    var location: String

    init(name: String, location: String) {
        self.location = location
        super.init(name: name)
    }
}

Sunt definite trei clase: albums, studio albums și live albums, cu ultimele două moștenind din Album. Deoarece orice instanță a LiveAlbum moștenește din Album poate fi tratată drept Album sau LiveAlbum – este ambele în același timp. Acesta se numește "polimorfism," dar înseamnă că poți scrie cod astfel:

var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var iTunesLive = LiveAlbum(name: "iTunes Live from SoHo", location: "New York")

var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]

Am creat un array care conține doar albume, dar am pus în interior două studio albums și un live album. Acest lucru este normal in Swift deoarece toate moștenesc din clasa Album , deci împărtășesc același comportament de bază.

Putem merge cu un pas mai departe pentru a demonstra cu adevărat cum funcționează polimorfismul. Să adăugăm metoda getPerformance() tuturor celor trei clase:

class Album {
    var name: String

    init(name: String) {
        self.name = name
    }

    func getPerformance() -> String {
        return "The album \(name) sold lots"
    }
}

class StudioAlbum: Album {
    var studio: String

    init(name: String, studio: String) {
        self.studio = studio
        super.init(name: name)
    }

    override func getPerformance() -> String {
        return "The studio album \(name) sold lots"
    }
}

class LiveAlbum: Album {
    var location: String

    init(name: String, location: String) {
        self.location = location
        super.init(name: name)
    }

    override func getPerformance() -> String {
        return "The live album \(name) sold lots"
    }
}

Metoda getPerformance() există în clasa Album, dar ambele clase copil o suprascriu. Când creăm un array care conține Albums, îl umplem de fapt cu subclase ale ale clasei Album: LiveAlbum și StudioAlbum. Pot să facă parte din array deoarece moștenesc din clasa Album, dar nu-și pierd niciodată clasa originală. Deci, putem scrie cod ca acesta:

var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var iTunesLive = LiveAlbum(name: "iTunes Live from SoHo", location: "New York")

var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]

for album in allAlbums {
    print(album.getPerformance())
}

Acesta va folosi în mod automat versiunea suprascrisă a metodei getPerformance() în funcție de subclasa respectivă. Acesta este polimorfismul în acțiune: Un obiect poate funcționa ca și clasa sa, dar și precum clasa părinte în același timp.

Convertirea tipurilor cu typecasting

De multe ori te vei afla în situația în care ai un obiect de un anume tip, dar tu știi că este de fapt alt tip. Din păcate, dacă Swift nu știe ceea ce tu știi, nu va face build codului tău. Deci, există o soluție și se numește typecasting: convertirea unui obiect de un tip în altul.

Sunt șanse să te chinui să înțelegi de ce așa ceva este necesar, dar pot să-ți dau un exemplu foarte simplu:

for album in allAlbums {
    print(album.getPerformance())
}

Acesta este for-ul nostru de mai devreme. Array-ul allAlbums conție tipul Album, dar noi știm că este una dintre subclase: StudioAlbum sau LiveAlbum. Swift nu știe asta, deci dacă încerci să scrii ceva de genul print(album.studio) va refuza să facă build deoarece doar obiectele StudioAlbum au acea proprietate.

Typecasting-ul în Swift vine în trei forme, dar de cele mai multe ori vei întâlni doar două: as? și as!, cunoscute drept downcasting opțional și downcasting forțat. Primul înseamnă "Cred că această conversie poate fi adevărată, dar ar putea eșua", iar cel de-al doilea înseamnă "Știu că această conversie este adevărată, și sunt fericit dacă aplicația mea crapă în cazul în care greșesc".

Notă: Când spun "conversie" nu vreau să spun că obiectul se transformă cu adevarat. De fapt, convertește doar modul în care Swift trateaza obiectul – Îi spui lui Swift că un obiect care el credea că este de tipul A este de fapt de tipul E.

Semnele întrebare și de exclamare ar trebui să-ți dea un indiciu despre ce se petrece, deoarece este similar cu opționalele. De exemplu, dacă scrii asta:

for album in allAlbums {
    let studioAlbum = album as? StudioAlbum
}

Swift va face studioAlbum să aibă tipul de date StudioAlbum?. Acesta este un studio album opțional: conversia se poate să fi funcționat, caz în care ai un studio album cu care poți să lucrezi, sau se poate să fi eșuat, caz în care ai un nil.

Acesta este cel mai des folosit cu if let pentru a desface automat rezultatul opțional, astfel:

for album in allAlbums {
    print(album.getPerformance())

    if let studioAlbum = album as? StudioAlbum {
        print(studioAlbum.studio)
    } else if let liveAlbum = album as? LiveAlbum {
        print(liveAlbum.location)
    }
}

Acesta va parcurge toate albumele și va afișa performance details, deoarece acesta îl au în comun clasa Album și subclasele sale. Apoi verifică dacă poate converti valoarea album în StudioAlbum, și dacă poate îi afișează numele studioului. Același lucru se face și pentru LiveAlbum în array.

Downcasting-ul forțat este atunci când ești foarte sigur că un obiect de un tip poate fi tratat drept alt tip, dar dacă te înșeli programul tău va crăpa. Downcasting-ul forțat nu trebuie să returneze o valoare opțională, deoarece spui că conversia va funcționa cu siguranță – Dacă nu ai dreptate, înseamnă că ai scris codul greșit.

Pentru a demonstra asta într-un mod care nu crapă, să scoatem live album pentru a avea doar studio album în array:

var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")

var allAlbums: [Album] = [taylorSwift, fearless]

for album in allAlbums {
    let studioAlbum = album as! StudioAlbum
    print(studioAlbum.studio)
}

Acesta este evident un exemplu născocit, deoarece dacă acesta ar fi fost cu adevărat codul tău ai fi schimbat pur și simplu allAlbums astfel încât să aibă tipul de date [StudioAlbum]. Totuși, demonstrează cum funcționează downcasting-ul, și programul nu va crăpa deoarece face presupunerile corecte.

Swift te lasă să faci downcasting în bucla array-ului, ceea ce în cazul acesta ar fi mult mai eficient. Dacă ai vrea să scrii acel downcasting forțat la nivel de array, ai fi scris asta:

for album in allAlbums as! [StudioAlbum] {
    print(album.studio)
}

Așa nu mai este nevoie să faci downcast fiecărui element în interiorul buclei. Din nou, ai face bine să fii sigur că fiecare element din array este de tipul StudioAlbums, altfel codul tău va crăpa.

Swift permite de asemenea downcasting-ul opțional la nivel de array, dar este puțin mai complicat deoarece trebuie să folosești operatorul de coalescență nil pentru a asigura ca acolo va fi mereu o valoare pentru buclă. Uite un exemplu:

for album in allAlbums as? [LiveAlbum] ?? [LiveAlbum]() {
    print(album.location)
}

Ceea ce vrea să spună este, “încearcă să convertești allAlbums să fie un array de obiecte LiveAlbum, dar dacă asta eșuează creează un array gol de live albums și folosește asta în schimb” – adică, nu fă nimic.

Convertirea tipurilor comune cu inițializatori

Typecasting-ul este folositor atunci când știi ceva ce Swift nu știe, de exemplu când ai un obiect de tipul A despre care Swift crede că este de tip B. Oricum, typecasting-ul este folositor doar când acele tipuri sunt într-adevăr ce spui tu – Nu poți forța un tip A într-un tip Z dacă nu au nicio legatură.

De exemplu, dacă ai un integer numit number, nu ai putea scrie cod astfel pentru a-l face string:

let number = 5
let text = number as! String

Asta înseamnă că nu poți forța un integer într-un string, deoarece acestea sunt două tipuri complet diferite. În schimb, poți crea un string nou folosind un integer, și Swift știe cum să le convertească. Diferența este subtilă: aceasta este o nouă valoare, și nu o re-interpretare a aceeași valori.

Deci, codul acela trebuie rescris astfel:

let number = 5
let text = String(number)
print(text)

Aceasta functionează doar pentru unele tipuri de date din Swift: poți converti valori integer și float în string și viceversa, de exemplu, dar dacă ai creat două structuri Swift nu le poate converti magic din una în alta – trebuie să scrii codul acela tu însuți.

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!