ProgramlamaiOS geliştirici

Swift'te protokol soyutlamalarının (protocol abstraction) nasıl çalıştığını ve bunların sınıf mirasından (class inheritance) nasıl farklılık gösterdiğini açıklayın. Protokolleri sınıf hiyerarşisi yerine ne zaman kullanmalısınız?

Hintsage yapay zeka asistanı ile mülakatları geçin

Cevap.

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:

  • Protokoller çoklu kompozisyonu destekler (birden fazla gereksinim).
  • Sıkı bir hiyerarşi oluşturmaz — genişletmesi daha kolay, mimariyi bozmuyor.
  • Hem değer türleri (struct/enum) hem de sınıflarla çalışabilir, bu miras ile mümkün değildir.

Kandırmaca soruları.

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() }

Yaygın hatalar ve antipatternlar

  • Ortak bir arayüz için şişkin bir üst sınıftan miras almak yerine protokollere bölmek
  • Protokollerde açık gereksinimler olmadan aşırı extension kullanımı
  • Associatedtype'li bir protokolü koleksiyonda saklama girişimi ([SomeProtocol]) — bu desteklenmez

Gerçek hayattan bir örnek

Olumsuz durum

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:

  • Temel sınıf aracılığıyla hızlı bir biçimde yeni ortak yöntemlerin entegre edilmesi

Eksiler:

  • Monolitik bir hiyerarşi ile gereksiz bağımlılıklar
  • Hiyerarşi dışında kötü tekrar kullanılabilirlik
  • Override yöntemlerinin çakışmaları

Olumlu durum

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:

  • Esneklik, kolay destekleme ve genişletilebilirlik
  • Farklı bağlamlarda daha iyi tekrar kullanılabilirlik

Eksiler:

  • API'nin dikkatli bir şekilde tasarlanmasını ve associatedtype bilgisi gerektirir.