Yapısal Patternler ve Component Composition
Decorator, Adapter, Facade, Composite ve Proxy patternlerinin React ve TypeScript'te nasıl evrildiğini keşfedin. HOC'ların ne zaman hook'lara yol verdiğini, adapterlerin third-party API'ları nasıl izole ettiğini ve facade'ların karmaşıklığı nasıl basitleştirdiğini öğrenin.
Yapısal patternler, objeler ve classlar arasındaki ilişkileri organize eder. Gang of Four, 1994'te Decorator, Adapter, Facade, Composite ve Proxy patternlerini C++ ve Smalltalk için belgeledi. Modern TypeScript ve React ekosistemlerinde bu patternler kaybolmadı - framework konvansiyonlarına, hook'lara ve type-safe wrapper'lara dönüştü.
Bu yazıda yapısal patternlerin React component composition'da nasıl ortaya çıktığını, higher-order componentlerin ne zaman hala önemli olduğunu ve TypeScript'in type system'ının klasik implementasyonları nasıl geliştirdiğini inceliyoruz.
Decorator Pattern: TypeScript'te Üç Farklı Anlam
"Decorator" terimi TypeScript ekosisteminde üç farklı şey ifade ediyor:
- Gang of Four Decorator Pattern: Objelere dinamik olarak davranış ekleme
- React Higher-Order Components (HOCs): Componentlere ek fonksiyonalite kazandırma
- TypeScript Decorator Syntax: Class/method decorator'lar için Stage 3 proposal
Her biri farklı problemleri çözüyor. Her yaklaşımın ne zaman değer kattığını inceleyelim.
React HOC'lar: Klasik Decorator Implementasyonu
Higher-order componentler, componentleri ek fonksiyonalite ile wrap ederek güçlendirir:
Bu çalışıyor, ama HOC'lar sorunlar yaratıyor:
Wrapper Hell: Birden fazla HOC stack'lemek çok derin component tree'leri oluşturuyor:
Props Collision: Birden fazla HOC aynı isimdeki prop'ları inject edebilir, çakışmalara sebep olur.
Ref Forwarding Karmaşıklığı: HOC katmanları arasında ref geçirmek explicit forwarding gerektirir. React 19'un forwardRef'i deprecated ettiğini unutma - ref'ler artık standart prop olarak geçirilebiliyor, bu pattern'i basitleştiriyor.
Belirsiz Data Flow: HOC'lar tarafından inject edilen prop'lar component signature'ında görünmüyor.
Modern Alternatif: Custom Hooks
Hook'lar aynı fonksiyonaliteyi daha temiz composition ile sağlıyor:
Hook'lar wrapper hell'i ortadan kaldırıyor, data flow'u explicit yapıyor ve doğal olarak compose oluyor. Her hook'un fonksiyonalitesi return değerlerinden açıkça görülüyor.
HOC'ların Hala Anlamlı Olduğu Durumlar
Hook'ların avantajlarına rağmen, HOC'lar şunlar için değerli:
Hook Kullanılamayan Library Kodları: Bazı context'ler component'leri internal'larını değiştirmeden wrap etmeyi gerektirir.
Visual Wrapper'lar: Component'lerin etrafına sadece görsel element ekleyen HOC'lar:
Legacy Class Component Entegrasyonu: Class component'lerden migrate ederken, HOC'lar modern hook'lara köprü oluyor.
TypeScript Decorator Syntax
TypeScript decorator'ları (Stage 3 proposal, TypeScript 5.0+ desteği) declarative metadata ve behavior modification sağlıyor:
TypeScript decorator'ları şunlar için iyi çalışıyor:
- Logging ve monitoring
- Validation ve authorization
- Caching ve memoization
- Performance tracking
Gerçek Senaryo: Analytics Tracking
Birden fazla component'e analytics eklemeyi düşünelim. Yaklaşımları karşılaştıralım:
HOC Yaklaşımı (verbose):
Hook Yaklaşımı (daha temiz):
Hook yaklaşımı component logic'ine doğal entegre oluyor, extra wrapper component'lerden kaçınıyor ve tracking'i component body'de explicit yapıyor.
Adapter Pattern: Uyumsuz Interface'leri Köprüleme
Adapter'lar bir interface'i diğerine çeviriyor. TypeScript'te third-party API'ları izole ediyorlar, daha kolay test ve gelecekte migration sağlıyorlar.
Problem: Third-Party API Uyuşmazlığı
External kütüphaneler genellikle domain modelinle eşleşmeyen interface'lere sahip:
Bu temsiller farklı amaçlara hizmet ediyor. Stripe'ın API'si onların backend'i için optimize, senin domain modelin ise business logic'in için.
Çözüm: Adapter Katmanı
Temsiller arasında çeviri yapan adapter'lar oluştur:
Stripe API'sini değiştirdiğinde, sadece adapter'ı güncellemen gerekiyor. Domain modelin stabil kalıyor.
TypeScript Mapped Types ile Adapter
TypeScript'in type system'ı mapped types kullanarak compile-time adapter'ları sağlıyor:
Bu pattern birçok API benzer wrapper yapılarını paylaştığında iyi çalışıyor.
React Component Props için Adapter
Adapter'lar third-party UI kütüphanelerini design system'ına entegre etmeye yardımcı oluyor:
Bu izolasyon, Material-UI'dan başka bir kütüphaneye geçişin sadece adapter component'i güncellemeyi gerektirmesi demek, her kullanım yerini değil.
Ne Zaman Adapter Oluşturmalı
Adapter oluştur:
- Değiştirebileceğin third-party servisler için (payment processor'lar, cloud provider'lar)
- Kötü TypeScript desteği olan API'lar için
- Karmaşık domain model transformasyonları gerektiren external servisler için
- Sık breaking change'ler yaşayan kütüphaneler için
Adapter oluşturma:
- Stabil, iyi type'lanmış kütüphaneler için (lodash, date-fns)
- Kontrolün altındaki internal utility'ler için
- Basit bire bir mapping'ler için (utility fonksiyonlar kullan)
Facade Pattern: Karmaşık Subsystem'ları Basitleştirme
Facade'lar karmaşık subsystem'lara basitleştirilmiş interface'ler sağlıyor. TypeScript'te initialization karmaşıklığını gizliyorlar, birden fazla servisi koordine ediyorlar ve implementation detaylarına coupling'i azaltıyorlar.
Problem: Karmaşık API Setup
AWS SDK v3 her servis için özel configuration, command objeler ve dikkatli error handling gerektiriyor:
Her operasyon AWS SDK konvansiyonları, command objeler ve data marshalling bilgisi gerektiriyor.
Çözüm: Birleşik Facade
Domain-specific operasyonlar sağlayan bir facade oluştur:
Facade, AWS SDK karmaşıklığını domain'e uygun methodların arkasında gizliyor. Tüketiciler uygulamanın vocabulary'si ile çalışıyor, AWS'inki ile değil.
Karmaşık Form İşlemleri için Facade
Formlar genellikle validation, submission, error handling ve analytics içeriyor:
Facade birden fazla subsystem'ı - API client, analytics, form state - tek bir method call'un arkasında koordine ediyor.
Barrel Export'lar: Tartışmalı Bir Facade
Barrel export'lar (index.ts dosyaları) moduller için public facade'lar oluşturuyor:
Bu internal yapıyı gizliyor, tüketicileri bozmadan internal'ları refactor etmeni sağlıyor.
Ancak: Atlassian'dan gelen 2024 araştırması, barrel export'ların build performance'ı önemli ölçüde zarar verebileceğini gösteriyor. Barrel dosyalarını kaldırdıktan sonra build sürelerini %75 azalttılar. Barrel'lar aracılığıyla 11k modül import eden Next.js sayfaları 3.5k direct import'a düştü (%68 azalma).
Öneri: Barrel export'ları sadece public library API'leri için kullan. Build performance'ın önemli olduğu internal proje modullerinde kullanma.
Facade'lar Ne Zaman Değer Katıyor
Facade oluştur:
- Ortak operasyonlar için birden fazla servisi koordine ederken
- Karmaşık initialization sequence'larını basitleştirirken
- Teknik API'lara domain-specific interface'ler sağlarken
- Uygulamaları third-party API değişikliklerinden izole ederken
Facade oluşturma:
- Basit obje oluşturma için (bu sadece gereksiz indirection)
- Tek methodları ek logic olmadan wrap ederken
- Zaten basit olan internal utility'ler için
Composite Pattern: React'in Doğal Yapısı
Composite pattern, bireysel objeleri ve composition'ları uniform olarak ele alıyor. React'in component modeli doğal olarak composite - component'ler başka component'leri içerebiliyor ve her ikisi de aynı şekilde ele alınıyor.
Klasik Composite Pattern
Ders kitabı örneği dosya sistemleri gibi hiyerarşik yapıları içeriyor:
Bu çalışıyor, ama React için verbose. Pattern sağlam - bireysel item'lar ve collection'ları uniform şekilde ele alma - ama implementation React'in doğal composition'ını kullanmıyor.
Modern React Yaklaşımı
React component'leri doğal olarak composite. Aynı logic'i idiomatic React ile:
Bu, React'in doğal recursion'ını ve type safety için discriminated union'ları kullanıyor. Composite doğası explicit class'lar yerine component yapısından geliyor.
Compound Components Pattern
Compound component pattern implicit state sharing ile esnek API'lar sağlıyor:
Bu pattern Radix UI, Headless UI ve Reach UI'da görünüyor. Esnek, composable API'lar için composite yapıyı context-based state sharing ile birleştiriyor.
Proxy Pattern: Lazy Loading ve Access Control
Proxy'ler objelere erişimi kontrol ediyor, lazy loading, caching, validation veya access control gibi davranışlar ekliyor. TypeScript hem class-based proxy'ler hem de runtime interception için JavaScript'in Proxy API'sini sağlıyor.
React.lazy ve Suspense
React'in built-in lazy loading'i bir proxy pattern - talep üzerine kod yüklemek için component render'ını intercept ediyor:
React.lazy component render'ını intercept ediyor, modülü sadece gerektiğinde yüklüyor. Suspense boundary proxy kodu fetch ederken loading state sağlıyor.
API Caching için Custom Proxy
Proxy'ler calling code'u değiştirmeden caching katmanları ekliyor:
Proxy, real client ile aynı interface'i implement ediyor, caching'i transparent şekilde ekliyor. Calling code cached mi fresh data mı kullandığını bilmiyor.
Validation için JavaScript Proxy API
JavaScript'in Proxy API'si runtime interception sağlıyor:
Proxy, property access ve assignment'ı intercept ediyor, runtime'da validation kurallarını enforce ediyor. Configuration objeleri veya invariant gerektiren domain entity'ler için iyi çalışıyor.
React Query bir Proxy Pattern
React Query, data fetching için proxy görevi görüyor, caching, loading state'leri ve refetching'i yönetiyor:
React Query data fetching'i intercept ediyor, şunları ekliyor:
- Configurable TTL ile otomatik caching
- Loading ve error state'leri
- Background refetching
- Cache invalidation
- Request deduplication
Component, React Query'nin useQuery'sini basit bir data fetch gibi ele alıyor, ama proxy karmaşık caching ve synchronization logic'i handle ediyor.
Access Control için Proxy
Proxy'ler authorization enforce edebilir:
Proxy, real implementation'ı değiştirmeden authorization ve logging enforce ediyor.
Yaygın Tuzaklar ve Dersler
Tuzak 1: HOC Wrapper Hell
Problem: Birden fazla HOC stack'lemek onlarca katman derin component tree'leri oluşturuyor, debugging'i zorlaştırıyor ve performance monitoring'i challenge haline getiriyor.
Çözüm: Çoğu cross-cutting concern için hook'lara migrate et. HOC'ları legacy entegrasyon veya gerçekten visual wrapper'lar için sakla.
Tuzak 2: Leaky Facade'lar
Problem: Implementation detaylarını expose eden facade'lar amacını yitiriyor.
Kötü Örnek:
İyi Örnek:
Ders: Facade'lar domain dilinle konuşmalı, underlying kütüphanenin diliyle değil.
Tuzak 3: Adapter Proliferation
Problem: Her external dependency için adapter oluşturmak net faydalar olmadan maintenance yükü yaratıyor.
Ne zaman adapter oluşturmalı:
- Swap etmeyi planladığın dependency'ler için (farklı cloud provider'lar, payment processor'lar)
- Type safety katmanları gerektiren kötü TypeScript desteği olan API'lar için
- Önemli domain model transformasyonu gerektiren external servisler için
Ne zaman adapter oluşturMAmalı:
- Stabil, iyi type'lanmış kütüphaneler için (lodash, date-fns)
- Kontrolün altındaki internal utility'ler için
- Basit bire bir mapping'ler için (utility fonksiyonlar kullan)
Tuzak 4: Composite Overengineering
Problem: Basit component hiyerarşileri için full composite pattern implement etmek.
Overkill:
Daha İyi:
Ders: React'in component modeli zaten composite davranış sağlıyor. Explicit pattern implementation nadiren gerekli.
Tuzak 5: Barrel Export Performance
Problem: Internal moduller için barrel export'lar (index.ts dosyaları) kullanmak build performance'ı zarar veriyor.
Araştırma: Atlassian barrel dosyalarını kaldırdıktan sonra build sürelerini %75 azalttı. Next.js uygulamaları modül import'larının 11k'dan 3.5k'ya düştüğünü gördü (%68 azalma).
Öneri: Barrel export'ları sadece public library API'leri için kullan. Internal moduller için direkt import et:
Önemli Çıkarımlar
HOC'lar → Hook'lar: Higher-order componentler (decorator pattern) büyük ölçüde daha temiz composition için hook'larla değiştirildi. HOC'lar legacy kod ve purely visual wrapper'lar için faydalı kalıyor.
Boundary'ler için Adapter: Sistem boundary'lerinde - external API'lar, third-party kütüphaneler - değişiklikleri izole etmek ve temiz domain modelleri sürdürmek için adapter'lar kullan. Stabil, iyi type'lanmış dependency'leri adapt etme.
Karmaşıklık için Facade: Facade'lar birden fazla subsystem'ı koordine etmede veya karmaşık initialization'ı basitleştirmede mükemmel. Basit obje oluşturma için facade oluşturmaktan kaçın - bu gereksiz indirection.
Composite React'in Doğası: React'in component modeli doğal olarak composite. Explicit composite pattern implementation nadiren gerekli - React'in doğal composition'ından yararlan.
Cross-Cutting Concern'ler için Proxy: Caching, lazy loading, access control ve validation için proxy'leri kullan. JavaScript'in Proxy API'si ve React Query gibi patternler, orijinal implementation'ları değiştirmeden güçlü interception sağlıyor.
Barrel Export'lar Tartışmalı: Sadece public library API'leri için dikkatli kullan. Internal barrel export'lar build performance'ı önemli ölçüde zarar veriyor.
TypeScript Pattern'leri Geliştiriyor: Mapped types, conditional types ve discriminated union'lar yapısal patternleri klasik implementasyonlardan daha güçlü ve type-safe yapıyor.
Decoration Yerine Composition: Modern React, daha iyi okunabilirlik ve maintainability için decoration patternleri (HOC'lar) yerine composition patternlerini (hook'lar, compound componentler, render props) tercih ediyor.
1994'teki yapısal patternler kaybolmadı. Framework konvansiyonlarına, type system özelliklerine ve modern mimari yaklaşımlara evrildi. Temel prensipleri anlamak - davranışı wrap etme, interface'leri adapt etme, karmaşıklığı basitleştirme, parça ve bütünü uniform ele alma, erişimi kontrol etme - bu patternleri modern codebase'lerde tanımana ve yeni sistemler inşa ederken uygun şekilde uygulamana yardımcı oluyor.
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.