Konu tarihi:
Protokol soyutlaması, Swift'te nesne yönelimli programlamadan (OOP) klasik mirasa karşı bir alternatif olarak ortaya çıkmıştır. Objective-C ve diğer OOP dillerinde "genelden özele" miras alma yaklaşımı hâkimken, Swift başlangıcından itibaren soyutlamayı sağlamak için protokolleri ana yol olarak teşvik etmiş, miras yerine kompozisyona vurgu yapmıştır.
Sorun:
Klasik miras alma, sıkı bir hiyerarşi varsayar: zorunlu override ile alt sınıflar ağacı. Bu, esnekliği kısıtlar, "kırılgan" koda, karmaşık yeniden yapılandırmalara ve temel üst sınıfların aşırı büyümesine neden olur. Ayrıca, Swift sınıflarda çoklu mirası desteklemediğinden, işlevselliğin tekrar kullanımı yalnızca başka mekanizmalar aracılığıyla mümkündür.
Çözüm:
Protokol soyutlaması, türün uygulaması gereken bir "gereksinimler seti" ilan etmeyi mümkün kılar. Protokoller, ortak mantığı sağlamak için genişletilebilir (extension), bu onları "mikzinler" fikrine yaklaştırır:
Kod örneği:
protocol Drawable { func draw() } extension Drawable { func draw() { print("Varsayılan çizim") } } struct Circle: Drawable {} let c = Circle() c.draw() // "Varsayılan çizim" yazdırır
Anahtar özellikler:
Protokolün genişletilmesi ile sınıfta normal bir uygulama arasındaki fark nedir?
Protokol genişlemesi aracılığıyla extension, kullanıcı tanımlı bir türde bu yöntemi uygulamadığı durumlar için varsayılan bir uygulama ekler. Eğer yöntem türde açıkça uygulanmışsa, yalnızca o çağrılır.
Örnek:
protocol Demo { func foo() } extension Demo { func foo() { print("varsayılan") } } struct X: Demo { func foo() { print("özel") } } X().foo() // "özel"
Protokolü ve onun extension'ını uygulayan bir türe protokol verisi olarak başvurulursa ne olur?
Eğer bir protokol bir yöntemi zorunlu olarak (gereksinim) ilan ederse, protokol türüne dönüştürme yapılsa bile belirli türün uygulaması kullanılır. Ancak extension'da önceki protokolde ilan edilmemiş yeni bir özellik eklenmişse, yalnızca extension aracılığıyla erişilebilir, protokol türünden değil.
Protokolü uygulayan farklı struct'ların örneklerini bir dizide saklamak mümkün mü?
Evet — "varlık" türleri sayesinde (örneğin, [Drawable]) heterojen koleksiyonlar saklayabilirsiniz:
struct Tri: Drawable { func draw() { print("Üçgen") } } let arr: [Drawable] = [Circle(), Tri()] arr.forEach { $0.draw() }
Bir firmada, tüm şekillerin (Circle, Square, Polygon) miras aldığı temel bir üst sınıf Shape vardı. Temel sınıf şişti çünkü her yeni şekli desteklemek için override kullanmak zorundaydık. Sistemi genişletmek giderek zorlaştı — her yeni tür ABI'yi bozuyor ve mevcut kodun yeniden yazılmasını gerektiriyordu.
Artılar:
Eksiler:
Birden fazla protokolden oluşan bir yapı kullanmaya başladık: Drawable, Colorable, Animatable. Artık her şekli, diğer yapıların değiştirilmesini gerektirmeden aynı anda "animasyonlu ve renkli" hale getirmek çok kolaydı. Yeni işlevsellik extension aracılığıyla ekleniyor.
Artılar:
Eksiler: