Tasarım desenleri, yazılım geliştirmede tekrarlayan sorunlara kanıtlanmış çözümlerdir. Gang of Four'un 1994'te tanımladığı 23 desen, bugün hala iOS geliştirmede her gün karşımıza çıkıyor. Ama hangi desen nerede kullanılır? Bu rehberde iOS'a en çok uyan 15 deseni gerçek kod örnekleriyle inceleyeceğiz.
💡 Hızlı Not: Bu rehber GoF Design Patterns, Martin Fowler'ın Patterns of Enterprise Application Architecture ve Apple'ın kendi framework tasarım kalıplarından derlendi.
İçindekiler
- Creational Patterns
- Singleton ve Anti-Pattern Riskleri
- Factory Method ve Abstract Factory
- Builder Pattern
- Structural Patterns
- Adapter ve Facade
- Decorator Pattern
- Behavioral Patterns
- Observer ve Strategy
- Coordinator Pattern
- Repository Pattern
Creational Patterns {#creational}
Creational pattern'ler nesne oluşturma mekanizmalarını soyutlar. iOS'ta en sık kullanılan üçünü inceleyelim.
Singleton ve Anti-Pattern Riskleri {#singleton}
Singleton, tüm uygulama boyunca tek bir instance garantisi sağlar. Apple framework'lerinde yaygın:
swift
1// Apple'ın singleton'ları2UIApplication.shared3FileManager.default4UserDefaults.standard5NotificationCenter.default6 7// Kendi singleton'ımız - Thread-safe8final class AnalyticsManager {9 static let shared = AnalyticsManager()10 11 private let queue = DispatchQueue(label: "analytics", qos: .utility)12 private var events: [AnalyticsEvent] = []13 14 private init() {} // Dışarıdan instance oluşturmayı engelle15 16 func track(_ event: AnalyticsEvent) {17 queue.async { [weak self] in18 self?.events.append(event)19 if self?.events.count ?? 0 >= 10 {20 self?.flush()21 }22 }23 }24 25 private func flush() {26 let batch = events27 events.removeAll()28 // API'ye gönder...29 }30}Singleton Anti-Pattern Tablosu
Sorun | Açıklama | Çözüm |
|---|---|---|
Test zorluğu | Global state mock'lanamaz | Protocol + DI kullan |
Gizli bağımlılık | Hangi class singleton kullanıyor belirsiz | Constructor injection |
Thread safety | Concurrent erişim race condition | Actor veya serial queue |
Memory | Uygulama ömrü boyunca bellekte | Gerçekten gerekli mi düşün |
Factory Method ve Abstract Factory {#factory}
Factory pattern, nesne oluşturma mantığını soyutlar:
swift
1// Factory Method2protocol ViewControllerFactory {3 func makeListVC() -> UIViewController4 func makeDetailVC(item: Item) -> UIViewController5}6 7class ProductViewControllerFactory: ViewControllerFactory {8 func makeListVC() -> UIViewController {9 let vm = ProductListViewModel(repository: ProductRepository())10 return ProductListViewController(viewModel: vm)11 }12 13 func makeDetailVC(item: Item) -> UIViewController {14 let vm = ProductDetailViewModel(product: item as! Product)15 return ProductDetailViewController(viewModel: vm)16 }17}18 19// Abstract Factory - Platform-specific UI20protocol UIComponentFactory {21 func makeButton(title: String) -> any ButtonComponent22 func makeTextField(placeholder: String) -> any TextFieldComponent23 func makeAlert(title: String, message: String) -> any AlertComponent24}25 26class iOSComponentFactory: UIComponentFactory {27 func makeButton(title: String) -> any ButtonComponent { iOSButton(title: title) }28 func makeTextField(placeholder: String) -> any TextFieldComponent { iOSTextField(placeholder: placeholder) }29 func makeAlert(title: String, message: String) -> any AlertComponent { iOSAlert(title: title, message: message) }30}Builder Pattern {#builder}
Karmaşık nesneleri adım adım oluşturmak için:
swift
1class URLRequestBuilder {2 private var url: URL3 private var method: String = "GET"4 private var headers: [String: String] = [:]5 private var body: Data?6 private var timeout: TimeInterval = 307 8 init(url: URL) { self.url = url }9 10 @discardableResult11 func method(_ method: String) -> Self { self.method = method; return self }12 13 @discardableResult14 func header(_ key: String, _ value: String) -> Self { headers[key] = value; return self }15 16 @discardableResult17 func jsonBody<T: Encodable>(_ body: T) -> Self {18 self.body = try? JSONEncoder().encode(body)19 headers["Content-Type"] = "application/json"20 return self21 }22 23 @discardableResult24 func timeout(_ seconds: TimeInterval) -> Self { self.timeout = seconds; return self }25 26 func build() -> URLRequest {27 var request = URLRequest(url: url, timeoutInterval: timeout)28 request.httpMethod = method29 request.httpBody = body30 headers.forEach { request.setValue($1, forHTTPHeaderField: $0) }31 return request32 }33}34 35// Kullanım - fluent API36let request = URLRequestBuilder(url: apiURL)37 .method("POST")38 .header("Authorization", "Bearer \(token)")39 .jsonBody(CreateUserRequest(name: "John", email: "[email protected]"))40 .timeout(15)41 .build()Structural Patterns {#structural}
Structural pattern'ler, nesneler arasındaki ilişkileri organize eder.
Adapter ve Facade {#adapter-facade}
swift
1// Adapter - 3rd party kütüphaneyi kendi interface'imize uyarla2protocol AnalyticsService {3 func logEvent(_ name: String, parameters: [String: Any])4}5 6// Firebase adapter7class FirebaseAnalyticsAdapter: AnalyticsService {8 func logEvent(_ name: String, parameters: [String: Any]) {9 Analytics.logEvent(name, parameters: parameters)10 }11}12 13// Mixpanel adapter14class MixpanelAnalyticsAdapter: AnalyticsService {15 func logEvent(_ name: String, parameters: [String: Any]) {16 Mixpanel.mainInstance().track(event: name, properties: parameters as? Properties)17 }18}19 20// Facade - Karmaşık alt sistemi basit interface ile sar21class AppStartupFacade {22 private let analytics: AnalyticsService23 private let crashReporter: CrashReporter24 private let featureFlags: FeatureFlagService25 private let pushManager: PushNotificationManager26 27 init(analytics: AnalyticsService, crashReporter: CrashReporter,28 featureFlags: FeatureFlagService, pushManager: PushNotificationManager) {29 self.analytics = analytics30 self.crashReporter = crashReporter31 self.featureFlags = featureFlags32 self.pushManager = pushManager33 }34 35 func start() async {36 crashReporter.initialize()37 await featureFlags.fetch()38 pushManager.requestPermission()39 analytics.logEvent("app_launched", parameters: [:])40 }41}Decorator Pattern {#decorator}
swift
1// Protocol2protocol DataService {3 func fetchData() async throws -> [Item]4}5 6// Base implementation7class RemoteDataService: DataService {8 func fetchData() async throws -> [Item] {9 // Network request...10 return try await apiClient.fetch()11 }12}13 14// Caching decorator15class CachingDataService: DataService {16 private let wrapped: DataService17 private let cache: NSCache<NSString, CacheEntry>18 19 init(wrapping service: DataService) {20 self.wrapped = service21 self.cache = NSCache()22 }23 24 func fetchData() async throws -> [Item] {25 if let cached = cache.object(forKey: "items"), !cached.isExpired {26 return cached.items27 }28 let items = try await wrapped.fetchData()29 cache.setObject(CacheEntry(items: items), forKey: "items")30 return items31 }32}33 34// Logging decorator35class LoggingDataService: DataService {36 private let wrapped: DataService37 38 init(wrapping service: DataService) { self.wrapped = service }39 40 func fetchData() async throws -> [Item] {41 let start = CFAbsoluteTimeGetCurrent()42 let items = try await wrapped.fetchData()43 let elapsed = CFAbsoluteTimeGetCurrent() - start44 print("📊 Fetched \(items.count) items in \(elapsed)s")45 return items46 }47}48 49// Compose decorators50let service: DataService = LoggingDataService(51 wrapping: CachingDataService(52 wrapping: RemoteDataService()53 )54)Behavioral Patterns {#behavioral}
Observer ve Strategy {#observer-strategy}
swift
1// Observer - Combine ile modern iOS2class ShoppingCart: ObservableObject {3 @Published var items: [CartItem] = []4 @Published var totalPrice: Decimal = 05 6 func addItem(_ item: CartItem) {7 items.append(item)8 recalculateTotal()9 }10}11 12// Strategy Pattern - Farklı sıralama algoritmaları13protocol SortStrategy {14 func sort<T: Comparable>(_ array: [T]) -> [T]15}16 17struct QuickSortStrategy: SortStrategy {18 func sort<T: Comparable>(_ array: [T]) -> [T] { array.sorted() }19}20 21struct RelevanceSortStrategy: SortStrategy {22 func sort<T: Comparable>(_ array: [T]) -> [T] { array.reversed() }23}24 25class ProductListViewModel {26 var sortStrategy: SortStrategy = QuickSortStrategy()27 28 func sortProducts(_ products: [Product]) -> [Product] {29 // Strategy pattern ile runtime'da algoritma değiştir30 sortStrategy.sort(products)31 }32}Coordinator Pattern {#coordinator}
iOS navigation'ı merkezi bir noktadan yönetmek için:
swift
1protocol Coordinator: AnyObject {2 var childCoordinators: [Coordinator] { get set }3 var navigationController: UINavigationController { get }4 func start()5}6 7class AppCoordinator: Coordinator {8 var childCoordinators: [Coordinator] = []9 let navigationController: UINavigationController10 11 init(navigationController: UINavigationController) {12 self.navigationController = navigationController13 }14 15 func start() {16 if UserDefaults.standard.bool(forKey: "isLoggedIn") {17 showMain()18 } else {19 showAuth()20 }21 }22 23 private func showAuth() {24 let authCoordinator = AuthCoordinator(nav: navigationController)25 authCoordinator.onLoginSuccess = { [weak self] in26 self?.removeChild(authCoordinator)27 self?.showMain()28 }29 childCoordinators.append(authCoordinator)30 authCoordinator.start()31 }32 33 private func showMain() {34 let mainCoordinator = MainTabCoordinator(nav: navigationController)35 childCoordinators.append(mainCoordinator)36 mainCoordinator.start()37 }38 39 private func removeChild(_ coordinator: Coordinator) {40 childCoordinators.removeAll { $0 === coordinator }41 }42}Repository Pattern {#repository}
Data katmanını soyutlayarak farklı data source'lar arasında geçiş yapabilirsin:
swift
1protocol ProductRepository {2 func getAll() async throws -> [Product]3 func getById(_ id: UUID) async throws -> Product4 func save(_ product: Product) async throws5 func delete(_ id: UUID) async throws6}7 8class RemoteProductRepository: ProductRepository {9 private let apiClient: APIClient10 11 func getAll() async throws -> [Product] {12 try await apiClient.get("/products")13 }14 15 func getById(_ id: UUID) async throws -> Product {16 try await apiClient.get("/products/\(id)")17 }18 19 func save(_ product: Product) async throws {20 try await apiClient.post("/products", body: product)21 }22 23 func delete(_ id: UUID) async throws {24 try await apiClient.delete("/products/\(id)")25 }26}Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var: **Kaynaklar:** - [Design Patterns: Elements of Reusable Object-Oriented Software (GoF)](https://en.wikipedia.org/wiki/Design_Patterns) - [Apple: Cocoa Design Patterns](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaDesignPatterns/CocoaDesignPatterns.html) - [Refactoring.Guru: Design Patterns](https://refactoring.guru/design-patterns)

