Gang of Four'un Ötesindeki Tasarım Kalıpları
JavaScript ve TypeScript ekosistemlerinden doğan modern kalıpları keşfedelim - hooks, compound components, render props ve GoF'un hiç karşılaşmadığı problemleri çözen repository pattern'leri.
Özet
Gang of Four, 1994'te C++ ve Smalltalk için pattern'leri dokümante etti. Asenkron programlamayı, component composition'ı, functional programming'i ya da reactive data flow'u öngöremezlerdi. JavaScript ve TypeScript ekosistemleri, 1994'te var olmayan problemleri çözmek için kendi pattern'lerini geliştirdi. Bu yazıda, gerçek web development ihtiyaçlarından doğan modern pattern'leri inceliyoruz: Stateful logic paylaşımı için React Hooks, esnek API'ler için Compound Components, data access abstraction için Repository Pattern ve doğal design pattern olarak ES Modules. Bunlar klasik pattern'lerin uyarlamaları değil - yeni problemler için yeni çözümler.
Pattern Düşüncesinin Evrimi
Yıllar içinde React codebase'leriyle çalışmak bana önemli bir şey öğretti: Gang of Four pattern'leri, sınırlı type system'lere sahip OOP dillerindeki spesifik problemleri çözüyor. JavaScript ve TypeScript'in farklı kısıtları ve yetenekleri var. First-class function'lar, closure'lar, ES module'ler, async/await ve JSX, C++ ya da Java'da imkansız veya pratik olmayan pattern'leri mümkün kılıyor.
Bu yazıdaki pattern'ler yeniden markalanmış GoF pattern'leri değil. React ve TypeScript topluluklarının gerçek problemleri çözerken organik olarak ortaya çıktılar:
- Stateful logic'i HOC wrapper cehennemini yaşamadan nasıl paylaşırız?
- Props patlaması olmadan esnek component API'leri nasıl oluştururuz?
- Test edilebilirlik için data access'i nasıl soyutlarız?
- Module-level state'i nasıl kapsülleriz?
Modern codebase'lerin gerçekte kullandığı pattern'leri keşfedelim.
Hooks Pattern: Wrapper'sız Stateful Logic
Hooks'un Çözdüğü Problem
Hook'lardan önce, React'te stateful logic paylaşmak Higher-Order Component'ler veya Render Props gerektiriyordu. Her iki yaklaşım da problemler yaratıyordu:
Bunu debug etmek acı verici. React DevTools, gerçek component'ine ulaşmadan önce altı seviye nesting gösteriyor. Birden fazla HOC benzer isimlerde prop'lar eklediğinde props collision yaygınlaşıyor.
Custom Hooks: Yeni Bir Pattern
Hook'lar, React'in state ve lifecycle'ını paylaşan function'lara yeniden kullanılabilir logic çıkarmamızı sağlıyor:
Fark önemli. Wrapper nesting yok, net data flow var ve mükemmel TypeScript inference. Her hook'un return type'ı otomatik olarak akıyor.
Karmaşık Logic'i Compose Etmek
Hook'lar doğal olarak compose oluyorlar. Form state yönetimi ile çalışırken karşılaşılabilecek gerçekçi bir örnek:
Bu pattern çalışıyor çünkü hook'lar function composition yoluyla compose oluyor. Her hook, diğer hook'ların tüketebileceği değerler döndürüyor. Inheritance hierarchy yok, wrapper component yok.
Kurallar ve Kısıtlamalar
Hook'ların ESLint tarafından zorlanan spesifik kuralları var:
- Hook'ları sadece üst seviyede çağır: Conditional'larda, loop'larda veya nested function'larda hook yok
- Hook'ları sadece React function'lardan çağır: Functional component'ler veya diğer hook'lar
- Dependency'ler eksiksiz olmalı:
useEffectveuseCallbackdependency'leri referans alınan tüm değerleri içermeli
Bu kısıtlamalar, React'in re-render'lar boyunca hook state'ini korumasını sağlıyor. İmplementasyon call order'a dayanıyor:
Kurallar başta kısıtlayıcı hissettiriyor ama güçlü composition pattern'lerini mümkün kılıyor.
Compound Components: Context ile Esnek API'ler
Problem: Props Patlaması
Yeniden kullanılabilir component'ler oluşturmak genellikle props patlamasına yol açıyor. Bir Select component düşün:
Her yeni özellik prop ekliyor. Component API'si yönetilemez hale geliyor.
Compound Components Pattern
Compound component'ler, prop'ları implicit state paylaşan birden fazla component'e dağıtıyor:
Compound pattern, props patlaması olmadan esneklik sağlıyor. Consumer'lar rendering'i kontrol ederken library state ve behavior'u yönetiyor.
Gerçek Dünya Örnekleri
Bu pattern React ekosisteminin her yerinde görülüyor:
Radix UI compound component'leri yaygın kullanıyor:
Headless UI erişilebilir component'ler için aynı pattern'i takip ediyor:
Pattern, esnekliğin sadelikten daha önemli olduğu component library'leri için iyi çalışıyor.
Repository Pattern: Data Access'i Soyutlama
Problem: Dağınık Data Logic
Abstraction olmadan, data access uygulamanın her yerine dağılıyor:
Bu yaklaşımın problemleri var:
- ORM değiştirmek codebase'in her yerinde değişiklik gerektiriyor
- Test etmek Prisma'yı doğrudan mock'lamayı gerektiriyor
- Business logic ve data access arasında net ayrım yok
- Caching veya logging'i uniform şekilde implement etmek zor
Repository Pattern İmplementasyonu
Repository pattern, data access'i interface'lerin arkasında merkezileştiriyor:
Faydalar ve Trade-off'lar
Faydalar:
- Test edilebilirlik: ORM internal'larını mock'lamadan implementasyonları değiştir
- Esneklik: Interface'i implement ederek Prisma'dan TypeORM'e geç
- Merkezileşmiş logic: Query pattern'leri, caching, logging tek yerde
- Net sınırlar: Business logic database detaylarını bilmiyor
Trade-off'lar:
- Daha fazla kod: Her entity repository interface ve implementasyon gerektiriyor
- Öğrenme eğrisi: Takım repository pattern'i anlamalı
- Abstraction maliyeti: ORM-spesifik özelliklere kolay erişemiyorsun
- Over-engineering riski: Basit CRUD app'lere buna gerek olmayabilir
Data-ağırlıklı uygulamalarda ekiplerle çalışmak bana repository pattern'in şu durumlarda işe yaradığını öğretti:
- Birden fazla data source (Postgres + Redis + S3)
- Service'lerde olmaması gereken karmaşık querying logic
- Yüksek test coverage gereksinimleri
- Gelecekte potansiyel ORM migration
Basit data access'li basit uygulamalar için, doğrudan ORM kullanımı genellikle daha temiz.
Provider Pattern: Context-Tabanlı Dependency Injection
Klasik Problem: Prop Drilling
Prop'ları birden fazla component katmanından geçirmek yorucu:
Üç ara component theme, user veya config kullanmıyor - sadece aşağı aktarıyorlar. Bu coupling yaratıyor ve refactoring'i acı verici hale getiriyor.
Context ile Provider Pattern
React Context, component tree'de derinlere prop drilling olmadan değer sağlıyor:
Herhangi bir derinlikteki component'ler, ara component'lerin bundan haberi olmadan theme'e erişebiliyor.
Birden Fazla Provider'ı Birleştirme
Gerçek uygulamaların birden fazla context'i var:
Bu nesting çalışıyor ama verbose. Yaygın bir pattern provider'ları birleştiriyor:
Performance Değerlendirmeleri
Context değer değiştiğinde tüm consumer'ları re-render ediyor. Memoization ile optimize et:
Sık değişen değerler için context'leri ayır:
Module Pattern: Design Pattern Olarak ES Module'ler
Pre-ES6: IIFE ve Revealing Module
ES module'lerden önce, JavaScript kapsülleme için IIFE kullanıyordu:
Bu çalışıyordu ama boilerplate gerektiriyordu ve static analysis yoktu.
Modern: ES Module'ler
ES module'ler doğal kapsülleme sağlıyor:
IIFE'ye göre faydaları:
- Static analysis: TypeScript ve bundler'lar import'ları anlıyor
- Tree shaking: Kullanılmayan export'lar build'de eleniyor
- Daha iyi tooling: Auto-import, go-to-definition düzgün çalışıyor
- Type safety: TypeScript module boundary'lerini zorunlu kılıyor
- Boilerplate yok: Wrapping function gerekmiyor
Module-Scoped Singleton'lar
ES module'ler doğal olarak singleton pattern implement ediyor:
Module caching, db'nin her yerde aynı instance olmasını garanti ediyor. Bu, private constructor ve static getInstance method'lu klasik singleton pattern'den daha temiz.
Module Initialization
Module'ler ilk import edildiğinde bir kez çalışıyor. Bunu initialization için kullan:
Container/Presenter: Ayrımı Yeniden Düşünmek
Klasik Pattern
Container/Presenter (Smart/Dumb veya Stateful/Stateless olarak da bilinir), data fetching'i presentation'dan ayırıyor:
Ayrımın faydaları:
- Test edilebilirlik: UserCard mock prop'larla test etmesi kolay
- Yeniden kullanılabilirlik: UserCard herhangi bir user object'iyle çalışıyor
- Storybook: Data fetching olmadan tüm state'lerde UserCard göster
Modern Alternatif: Co-location
Hook'lar, fedakarlık yapmadan data ve presentation'ı birlikte tutmayı mümkün kılıyor:
Hook'ları mock'layarak test etmek yine basit:
Storybook, hook'ları story seviyesinde mock'layarak çalışıyor:
Container/Presenter Hala Ne Zaman Mantıklı
Katı ayrım şu durumlarda hala değerli:
- App'ler arasında paylaşılan presentation component'leri: Design system component'leri
- Server-side rendering: Platform başına farklı data fetching pattern'leri
- Karmaşık prop interface'leri: Data ve display arasında net contract
- Ekip sınırları: Farklı ekipler data'ya ve UI'a sahip
Çoğu uygulama kodu için, hook'larla co-location test edilebilirlikten ödün vermeden daha iyi developer experience sağlıyor.
Render Props: Gelişmiş Kontrol Pattern
Hook'lar Yeterli Olmadığında
Hook'lar çoğu senaryo için çalışıyor ama consumer'ların rendering kontrolüne ihtiyacı olduğunda başarısız oluyor:
Render Props Pattern
Rendering kontrolünü consumer'lara ver:
Render Props vs Hook'lar
Hook'ları kullan:
- Consumer'lar sadece data'ya ihtiyaç duyduğunda
- Rendering kullanımlar arasında tutarlı olduğunda
- Logic composition, rendering kontrolünden daha önemli olduğunda
Render props kullan:
- Consumer'ların tam rendering kontrolüne ihtiyacı olduğunda
- Loading/error state'leri önemli ölçüde değiştiğinde
- Component karmaşık UI state yönetiyorsa (modal'lar, dropdown'lar)
React Router'dan gerçek örnek:
Modern React Router v6, çoğu kullanım için hook'lara (useParams, useNavigate) geçti, ama render props gelişmiş senaryolar için kaldı.
Seri Özeti: Geçmişten Günümüze Pattern'ler
Pattern'leri 1994'ten 2025'e keşfeden dört yazıyı tamamladık:
Post 1: Creational Pattern'ler ES module'lerin, object spread'in ve TypeScript'in type system'inin çoğu creational pattern'i nasıl değiştirdiğini gösterdi. Singleton module export'u oldu, prototype spread operator'ü oldu, factory discriminated union oldu. Builder pattern karmaşık konfigürasyonlar için hala değerli.
Post 2: Structural Pattern'ler React'in composition model'inin structural pattern'leri nasıl içine aldığını inceledi. Decorator hook'lar ve HOC'lar oldu, facade karmaşık API'leri basitleştirdi, composite doğal olarak component tree'lere map'lendi, adapter harici bağımlılıkları izole etti.
Post 3: Behavioral Pattern'ler observer'ın reactive programming'e evrimini araştırdı. RxJS, Redux ve hook'lar daha iyi error handling ve cancellation ile modern observer implementasyonlarını temsil ediyor. Strategy function'lar oldu, command undo/redo'yu güçlendiriyor, state machine'ler invalid state'leri önlüyor.
Post 4: Modern Pattern'ler JavaScript/TypeScript ekosistemlerinden doğan pattern'leri katalogladı. Hook'lar wrapper cehennemini çözdü, compound component'ler esnek API'ler sağladı, repository data access'i soyutladı, module'ler doğal singleton'lar oldu.
Ne Değişti ve Neden
Gang of Four 1994'te C++ ve Smalltalk ile çalıştı. Bu dillerde vardı:
- Statik class hierarchy'leri (inheritance birincil mekanizma)
- Sınırlı type inference
- First-class function yok
- Module system yok
- Asenkron primitive'ler yok
- Component composition yok
Modern JavaScript ve TypeScript'te var:
- First-class function'lar ve closure'lar
- Sofistike type inference
- Tree shaking'li ES module'ler
- Async/await ve Promise'ler
- Component composition (React/Vue/Svelte)
- Reactive programming primitive'leri
Farklı kısıtlamalar farklı pattern'lere yol açıyor. Pattern'lerin çözdüğü problemler hala geçerli - implementasyonlar evrildi.
Pattern Seçim Framework'ü
Bir tasarım kararıyla karşılaştığında:
- Problemi tanımla: Hangi spesifik problem çözülmeli?
- Dil özelliklerini kontrol et: Dil bunu zaten çözüyor mu?
- Framework'leri değerlendir: React/Next.js/vb bir çözüm sağlıyor mu?
- Trade-off'ları değerlendir: Pattern değer mi katıyor yoksa sadece karmaşıklık mı?
- Test etmeyi düşün: Bu kod'u test etmeyi kolaylaştırıyor mu zorlaştırıyor mu?
- Ekip context'ini değerlendir: Ekip bunu anlayıp maintain edebilir mi?
Modern Pattern Checklist
Herhangi bir pattern implement etmeden önce sor:
- TypeScript'in type system'i bunu çözüyor mu? (Discriminated union'lar genellikle factory pattern'i ortadan kaldırıyor)
- Hook'lar bunu çözüyor mu? (Stateful logic paylaşımı için genellikle evet)
- Composition bunu çözüyor mu? (React component'leri doğal olarak compose oluyor)
- Module'ler bunu çözüyor mu? (ES module'ler birçok creational pattern'i değiştiriyor)
- Bu test edilebilirliği iyileştiriyor mu? (Eğer değilse, yeniden düşün)
- Bu takım arkadaşlarına net olacak mı? (Akıllı pattern'ler maintainability'ye zarar verebilir)
İleriye Bakış
Diller ve framework'ler değiştikçe pattern'ler evrilmeye devam edecek:
React Server Component'leri server/client composition için yeni pattern'ler getiriyor. Server ve client kodu arasındaki sınır yeni abstraction zorlukları yaratıyor. React 19 (Aralık 2024) ile RSC stabil ve production-ready hale geldi, server-first pattern'leri mainstream development'a getirdi.
Signal'ler ve fine-grained reactivity (SolidJS, Vue 3, Preact Signals) React'in component re-rendering model'inden farklı state yönetim pattern'lerini temsil ediyor. Bu yaklaşım, virtual DOM diffing'e alternatif olarak birden fazla framework'te benimseniyor.
TypeScript'te type-level programming daha önce runtime pattern gerektiren kısıtlamaları compile time'da encode etmeyi mümkün kılıyor.
Edge computing ve distributed system'ler network boundary'leri, caching ve eventual consistency ile başa çıkmak için pattern'ler getiriyor.
Bir sonraki nesil pattern'ler, henüz karşılaşmaya başladığımız problemleri çözecek. Klasik pattern'leri ve modern evrimlerini anlamak, yeni ortaya çıkan pattern'leri tanımaya ve trade-off'larını değerlendirmeye hazırlıyor.
Tüm Pattern'lerde Ortak Prensipler
Dönem veya dilden bağımsız olarak, iyi pattern'ler özellikleri paylaşıyor:
- Gerçek problemleri çöz: Pattern için pattern yapma
- Maintainability'yi iyileştir: Kod anlaşılması ve değiştirilmesi daha kolay olmalı
- Test etmeyi sağla: İyi pattern'ler kod'u daha test edilebilir yapıyor, daha az değil
- Coupling'i azalt: Component'ler implementasyonlara değil abstraction'lara bağımlı olmalı
- Net niyet: Pattern kullanımı tasarım kararlarını iletmeli
- Context'e uygun: Library'ler için işe yarayan uygulamalardan farklı
- Ekiple uyumlu: Pattern'ler ekibin becerileri ve codebase convention'larıyla eşleşmeli
Son Düşünceler
Pattern'lerle çalışmak bana bunların araç olduğunu öğretti, kural değil. Gang of Four pattern'leri spesifik context'lerde spesifik problemleri çözdü. Modern pattern'ler farklı context'lerde farklı problemleri çözüyor. İkisini de anlamak, her birinin ne zaman uygulanacağını tanımaya yardımcı oluyor.
Kitaplardan veya blog yazılarından pattern'leri cargo-cult yapma. Her pattern'in çözdüğü problemi anla, bu problemin codebase'inde olup olmadığını değerlendir ve en uygun çözümü seç - ister klasik pattern, ister modern pattern, isterse hiç pattern olmasın.
En iyi kod genellikle pattern'leri görünmez kullanıyor. Okuyucular açık pattern implementasyonu görmeden çözümü tanıyor. Hedef bu: Pattern'leri yardımcı olduklarında kullanarak ve olmadıklarında atlayarak problemleri net ve sürdürülebilir şekilde çöz.
Pattern'ler tasarımı tartışmak için bir kelime dağarcığı, onu implement etmek için bir reçete değil. Ekibini etkilemek için değil, onlarla iletişim kurmak için kullan.
Klasik Tasarım Kalıplarına Modern Bakış
Klasik Gang of Four tasarım kalıplarının modern TypeScript, React ve fonksiyonel programlama bağlamında nasıl evrildiğini inceleyen kapsamlı bir seri. Klasik kalıpların hala ne zaman geçerli olduğunu, ne zaman yerini yeni yaklaşımlara bıraktığını ve temel prensiplerin modern kod tabanlarında nasıl ortaya çıktığını öğren.