2025-11-06
Reaktif Programlama Çağında Davranışsal Kalıplar
Observer, Strategy, Command, State ve Mediator kalıplarının TypeScript'te RxJS, Redux, XState ve modern reaktif programlama paradigmalarıyla nasıl evrildiğini keşfediyoruz.
Özet
Davranışsal (behavioral) kalıplar, nesnelerin nasıl iletişim kurduğunu ve sorumlulukların nasıl dağıtıldığını tanımlar. Gang of Four, Observer, Strategy, Command, State ve Mediator kalıplarını C++ ve Smalltalk için belgeledi - bu kalıpları uygulamanın önemli miktarda boilerplate gerektirdiği diller. Modern TypeScript ile RxJS, Redux ve React hooks bu kalıpları uygulama şeklimizi temelden değiştirdi. Bazıları framework convention’larına dönüştü, diğerleri reaktif paradigmalara evrildi, bazıları ise klasik formlarında şaşırtıcı derecede güncel kaldı.
Bu yazıda davranışsal kalıpların modern JavaScript/TypeScript uygulamalarında nasıl göründüğünü inceliyoruz. Klasik Observer’dan RxJS Observable’lara evrimi, Strategy pattern’in first-class function’larla nasıl basitleştiğini, Redux action’larının neden gizli Command pattern olduğunu, XState’in State pattern’i nasıl pratik hale getirdiğini ve Mediator pattern’in ne zaman tight coupling’i önlediğini keşfedeceğiz. Amaç: bu kalıpların ne zaman değer kattığını ve ne zaman gereksiz karmaşıklık olduğunu anlamak.
Observer Pattern: Callback’lerden Reaktif Stream’lere
Observer pattern, bir nesnedeki değişikliklerin bağımlı nesnelerde güncelleme tetiklediği one-to-many bağımlılık ilişkilerini sağlar. Modern web geliştirmede belki de en etkili pattern, her ne kadar tanımayabilsen de.
Klasik İmplementasyon
Ders kitabı Observer pattern’i açık subject-observer ilişkileri gerektirir:
interface Observer {
update(data: any): void;
}
class Subject {
private observers: Observer[] = [];
subscribe(observer: Observer): void {
this.observers.push(observer);
}
unsubscribe(observer: Observer): void {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
notify(data: any): void {
this.observers.forEach(o => o.update(data));
}
}
// Kullanım
class ConcreteObserver implements Observer {
update(data: any): void {
console.log('Güncelleme alındı:', data);
}
}
const subject = new Subject();
const observer = new ConcreteObserver();
subject.subscribe(observer);
subject.notify({ value: 42 });
Bu çalışır ama verbose ve modern uygulamaların ihtiyaç duyduğu özellikleri eksik: backpressure handling, error propagation, completion signal’ları, operator composition.
Node.js EventEmitter Evrimi
Node.js, EventEmitter ile daha esnek bir observer implementasyonu getirdi:
import { EventEmitter } from 'events';
class DataStream extends EventEmitter {
start(): void {
setInterval(() => {
this.emit('data', { timestamp: Date.now() });
}, 1000);
}
}
const stream = new DataStream();
stream.on('data', (data) => {
console.log('Alındı:', data);
});
stream.on('error', (error) => {
console.error('Hata:', error);
});
stream.start();
EventEmitter klasik Observer’ı şunlarla geliştirdi:
- Tek notification method yerine isimlendirilmiş event’ler
- Event başına birden fazla listener
- Error event desteği
- Tanıdık
.on()ve.emit()API’si
Ama hâlâ modern async senaryolar için gereken yetenekleri eksik: cancellation, debounce veya map gibi operator’ler, otomatik cleanup.
RxJS: Evrimleşmiş Observer Pattern
RxJS (Reactive Extensions for JavaScript), Observer pattern’in reaktif programlamaya tam evrimini temsil eder:
import { Observable, Subject, of } from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
map,
filter,
catchError
} from 'rxjs';
// Reaktif operator'lerle search input
const searchInput$ = new Subject<string>();
searchInput$.pipe(
debounceTime(300), // Yazmayı durdurduktan 300ms sonra bekle
distinctUntilChanged(), // Sadece değer değiştiyse
map(term => term.toLowerCase()),
filter(term => term.length >= 3), // Min 3 karakter
catchError(error => {
console.error('Arama hatası:', error);
return of([]); // Observable döndürmeli
})
).subscribe(term => {
console.log('Aranıyor:', term);
// Arama sonuçlarını getir
});
// Değer emit et
searchInput$.next('rea');
searchInput$.next('react');
searchInput$.next('reactive');
RxJS’i güçlü yapan:
- Composable operator’ler: Transformation’ları declarative olarak zincirle
- Error handling: Error’lar stream boyunca yayılır
- Completion signal’ları: Stream ne zaman bittiğini bil
- Backpressure: Hızlı producer’ları
throttlegibi operator’lerle handle et - Cancellation: Unsubscribe execution’ı durdurur
- Hot vs Cold: Execution’ın ne zaman başladığını kontrol et
Gerçek Dünya Senaryosu: WebSocket Dashboard
Gerçek zamanlı hisse senedi fiyat dashboard’u RxJS’in Observer evrimini gösterir:
import { Observable, merge, interval, Subject } from 'rxjs';
import {
filter,
map,
scan,
share,
retry,
takeUntil
} from 'rxjs';
interface StockPrice {
symbol: string;
value: number;
timestamp: number;
}
// Observable olarak WebSocket
function createStockStream(url: string): Observable<StockPrice> {
return new Observable<StockPrice>(subscriber => {
const ws = new WebSocket(url);
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
subscriber.next(data);
} catch (error) {
subscriber.error(error);
}
};
ws.onerror = (error) => {
subscriber.error(error);
};
ws.onclose = () => {
subscriber.complete();
};
// Cleanup fonksiyonu
return () => {
console.log('WebSocket bağlantısı kapatılıyor');
ws.close();
};
});
}
const stockPrices$ = createStockStream('wss://stocks.example.com').pipe(
retry(3), // Bağlantı hatasında tekrar dene
share() // Subscriber'lar arasında bağlantı paylaş
);
// Farklı işlemlerle birden fazla observer
// Observer 1: AAPL chart'ını güncelle
stockPrices$.pipe(
filter(price => price.symbol === 'AAPL'),
map(price => price.value)
).subscribe(value => {
updateChart('AAPL', value);
});
// Observer 2: Portfolio toplamını hesapla
stockPrices$.pipe(
scan((acc, price) => {
acc[price.symbol] = price.value;
return acc;
}, {} as Record<string, number>),
map(prices => calculatePortfolioValue(prices))
).subscribe(total => {
updatePortfolioDisplay(total);
});
// Observer 3: Önemli değişikliklerde alert
stockPrices$.pipe(
filter(price => Math.abs(price.value - getPreviousPrice(price.symbol)) > 10),
map(price => ({
symbol: price.symbol,
change: price.value - getPreviousPrice(price.symbol)
}))
).subscribe(alert => {
showNotification(`${alert.symbol} ${alert.change} hareket etti`);
});
// Component unmount'tan sonra cleanup
const unsubscribe$ = new Subject<void>();
stockPrices$.pipe(
takeUntil(unsubscribe$)
).subscribe();
// Sonra: tüm subscription'ları temizle
function cleanup() {
unsubscribe$.next();
unsubscribe$.complete();
}
Klasik Observer’a göre temel iyileştirmeler:
- Otomatik cleanup: Unsubscribe WebSocket’i kapatır
- Error handling: Bağlantı hataları retry mantığını tetikler
- Paylaşılan bağlantı: Birden fazla subscriber tek WebSocket kullanır
- Declarative filter’lar: Her observer sadece ilgili datayı işler
- Koordineli cleanup:
takeUntilmemory leak olmamasını sağlar
Redux: State için Observer Pattern
Redux, uygulama state yönetimi için Observer pattern uygular:
import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';
interface User {
id: string;
name: string;
}
interface AppState {
count: number;
user: User | null;
}
// Modern Redux Toolkit yaklaşımı (createStore deprecated)
const appSlice = createSlice({
name: 'app',
initialState: { count: 0, user: null } as AppState,
reducers: {
increment: (state) => {
state.count += 1;
},
decrement: (state) => {
state.count -= 1;
},
setUser: (state, action: PayloadAction<User>) => {
state.user = action.payload;
}
}
});
const { increment, decrement, setUser } = appSlice.actions;
const store = configureStore({
reducer: appSlice.reducer
});
// State değişikliklerini subscribe et (gözle)
const unsubscribe = store.subscribe(() => {
const state = store.getState();
console.log('State değişti:', state);
// Yeni state'e göre UI'ı güncelle
});
// Action'ları dispatch et
store.dispatch(increment());
store.dispatch(setUser({ id: '1', name: 'John' }));
// Sonra: cleanup
unsubscribe();
React-Redux component’leri observer olarak bağlar:
Note: Modern React-Redux,
connectHOC yerine hook’ları (useSelector/useDispatch) tercih eder. Aşağıdaki örnek eğitim amaçlı eski pattern’i gösterir.
import { connect } from 'react-redux';
interface CounterProps {
count: number;
increment: () => void;
decrement: () => void;
}
function Counter({ count, increment, decrement }: CounterProps) {
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}
// Component store'u gözler, değişikliklerde re-render olur
export default connect(
(state: AppState) => ({
count: state.count
}),
(dispatch) => ({
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' })
})
)(Counter);
Modern Alternatif: Zustand
Zustand, Observer pattern’i hook’lar ve minimal boilerplate ile basitleştirir:
import { create } from 'zustand';
interface StoreState {
count: number;
user: User | null;
increment: () => void;
decrement: () => void;
setUser: (user: User) => void;
}
// Store oluştur (subject)
const useStore = create<StoreState>((set) => ({
count: 0,
user: null,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
setUser: (user) => set({ user })
}));
// Component otomatik olarak ilgili state'i gözler
function Counter() {
// Sadece count değiştiğinde re-render olur
const count = useStore(state => state.count);
const increment = useStore(state => state.increment);
const decrement = useStore(state => state.decrement);
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}
Zustand’ın Observer faydaları:
- Otomatik subscription’lar: Component’ler hook’lar aracılığıyla subscribe olur
- Granular update’ler: Sadece seçilen state değiştiğinde re-render
- Boilerplate yok: Action, reducer ya da connect HOC yok
- TypeScript dostu: Tam type inference
- Middleware desteği: DevTools, persist, immer entegrasyonu
Hangi Observer Varyantını Kullanmalı
RxJS kullan:
- Karmaşık async koordinasyon (birden fazla stream birleştir)
- Gelişmiş operator’ler gerekli (debounce, throttle, retry)
- WebSocket veya SSE bağlantıları
- Event stream işleme
- Backpressure handling kritik
Redux kullan:
- Karmaşık state’li büyük uygulama
- Time-travel debugging gerekli
- Birçok component aynı state’e erişir
- Middleware ekosistemi değerli (saga’lar, thunk’lar)
- Ekip Redux pattern’lerine aşina
Zustand kullan:
- Orta ölçekli uygulama
- Özellikler yerine sadelik istiyorsun
- Redux DevTools gerekmez
- Connect HOC yerine hook’ları tercih et
- TypeScript desteği öncelik
Plain EventEmitter kullan:
- Node.js backend servisleri
- Modül içinde basit pub/sub
- Operator veya backpressure gerekmez
- Minimal dependency istiyorsun
Strategy Pattern: Class’lar Yerine Composition
Strategy pattern, algoritmaların runtime’da seçilmesini sağlar. First-class function’lara sahip dillerde, Strategy pattern genellikle class’lara ihtiyaç duymaz - function’lar daha iyi çalışır.
Klasik Strategy Pattern
Ders kitabı yaklaşımı interface’ler ve concrete implementation’lar kullanır:
interface PaymentStrategy {
pay(amount: number): Promise<void>;
}
class CreditCardStrategy implements PaymentStrategy {
constructor(
private cardNumber: string,
private cvv: string
) {}
async pay(amount: number): Promise<void> {
console.log(`Kart ${this.cardNumber} ile $${amount} ödeniyor`);
// Kredi kartı API çağrısı
await this.processCreditCard(amount);
}
private async processCreditCard(amount: number): Promise<void> {
// İmplementasyon detayları
}
}
class PayPalStrategy implements PaymentStrategy {
constructor(private email: string) {}
async pay(amount: number): Promise<void> {
console.log(`PayPal ile ${this.email} adresine $${amount} ödeniyor`);
// PayPal API çağrısı
await this.processPayPal(amount);
}
private async processPayPal(amount: number): Promise<void> {
// İmplementasyon detayları
}
}
class CryptoStrategy implements PaymentStrategy {
constructor(private walletAddress: string) {}
async pay(amount: number): Promise<void> {
console.log(`Cüzdan ${this.walletAddress} ile $${amount} ödeniyor`);
// Crypto ödeme işleme
await this.processCrypto(amount);
}
private async processCrypto(amount: number): Promise<void> {
// İmplementasyon detayları
}
}
// Context class strategy kullanır
class PaymentProcessor {
constructor(private strategy: PaymentStrategy) {}
setStrategy(strategy: PaymentStrategy): void {
this.strategy = strategy;
}
async processPayment(amount: number): Promise<void> {
await this.strategy.pay(amount);
}
}
// Kullanım
const processor = new PaymentProcessor(
new CreditCardStrategy('4111-1111-1111-1111', '123')
);
await processor.processPayment(100);
processor.setStrategy(new PayPalStrategy('[email protected]'));
await processor.processPayment(50);
Bu çalışır ama verbose. Her ödeme yöntemi için gerçekten class’lara ihtiyacımız var mı?
Strategy Olarak Function’lar
JavaScript/TypeScript’te first-class function’lar var. Strategy’ler basit function’lar olabilir:
type PaymentStrategy = (amount: number) => Promise<void>;
const creditCardPayment: PaymentStrategy = async (amount) => {
console.log(`Kredi kartı ile $${amount} ödeniyor`);
// Kredi kartı işleme
};
const paypalPayment: PaymentStrategy = async (amount) => {
console.log(`PayPal ile $${amount} ödeniyor`);
// PayPal işleme
};
const cryptoPayment: PaymentStrategy = async (amount) => {
console.log(`Crypto ile $${amount} ödeniyor`);
// Crypto işleme
};
// Context function kabul eder
class PaymentProcessor {
constructor(private strategy: PaymentStrategy) {}
setStrategy(strategy: PaymentStrategy): void {
this.strategy = strategy;
}
async processPayment(amount: number): Promise<void> {
await this.strategy(amount);
}
}
// Daha da basit: class gerekmez
async function processPayment(
amount: number,
strategy: PaymentStrategy
): Promise<void> {
await strategy(amount);
}
// Kullanım
await processPayment(100, creditCardPayment);
await processPayment(50, paypalPayment);
Function strategy’lerinin faydaları:
- Daha az boilerplate (class tanımları yok)
- Daha esnek (closure’lar context yakalar)
- Test etmesi daha kolay (function mock’lamak class mock’lamaktan daha basit)
- Doğal composition (higher-order function’lar)
Closure’larla Strategy
Closure’lar private state’li strategy’leri sağlar:
type PaymentStrategy = (amount: number) => Promise<void>;
function createCreditCardPayment(
cardNumber: string,
cvv: string
): PaymentStrategy {
// Closure kart detaylarını yakalar
return async (amount: number) => {
console.log(`Son 4 hanesi ${cardNumber.slice(-4)} olan karta $${amount} ödeniyor`);
// Yakalanan credential'larla işle
await processCreditCard(cardNumber, cvv, amount);
};
}
function createPayPalPayment(email: string): PaymentStrategy {
return async (amount: number) => {
console.log(`PayPal hesabı ${email} ile $${amount} ödeniyor`);
await processPayPal(email, amount);
};
}
// Kullanım
const creditCard = createCreditCardPayment('4111-1111-1111-1111', '123');
const paypal = createPayPalPayment('[email protected]');
await processPayment(100, creditCard);
await processPayment(50, paypal);
React: Prop’lar Aracılığıyla Strategy
React’te strategy’ler genellikle render prop’lar veya component prop’lar olarak görünür:
interface DataGridProps<T> {
data: T[];
renderRow: (item: T, index: number) => JSX.Element; // Strategy
renderEmpty?: () => JSX.Element;
renderLoading?: () => JSX.Element;
}
function DataGrid<T>({
data,
renderRow,
renderEmpty,
renderLoading
}: DataGridProps<T>) {
if (loading && renderLoading) {
return renderLoading();
}
if (data.length === 0 && renderEmpty) {
return renderEmpty();
}
return (
<div className="data-grid">
{data.map((item, i) => (
<div key={i} className="grid-row">
{renderRow(item, i)}
</div>
))}
</div>
);
}
// Farklı rendering strategy'leri
interface User {
id: string;
name: string;
email: string;
avatar: string;
}
// Strategy 1: Kart düzeni
<DataGrid
data={users}
renderRow={(user) => (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
)}
/>
// Strategy 2: Liste düzeni
<DataGrid
data={users}
renderRow={(user) => (
<div className="user-list-item">
<span>{user.name}</span>
<span>{user.email}</span>
</div>
)}
/>
// Strategy 3: Tablo satırı
<table>
<DataGrid
data={users}
renderRow={(user) => (
<>
<td>{user.name}</td>
<td>{user.email}</td>
</>
)}
/>
</table>
Gerçek Senaryo: Form Validation
Form validation, composable strategy pattern’den faydalanır:
type ValidationStrategy = (value: string) => string | null;
// Tekil validation strategy'leri
const required: ValidationStrategy = (value) => {
return value.trim() ? null : 'Bu alan zorunludur';
};
const email: ValidationStrategy = (value) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value) ? null : 'Geçersiz email adresi';
};
const minLength = (min: number): ValidationStrategy => (value) => {
return value.length >= min
? null
: `En az ${min} karakter olmalı`;
};
const maxLength = (max: number): ValidationStrategy => (value) => {
return value.length <= max
? null
: `En fazla ${max} karakter olmalı`;
};
const phone: ValidationStrategy = (value) => {
const phoneRegex = /^\d{10}$/;
return phoneRegex.test(value) ? null : 'Geçersiz telefon numarası';
};
// Birden fazla strategy'yi compose et
function composeValidations(...strategies: ValidationStrategy[]): ValidationStrategy {
return (value: string) => {
for (const strategy of strategies) {
const error = strategy(value);
if (error) return error;
}
return null;
};
}
// Compose edilmiş strategy'lerle field konfigürasyonları
interface FieldConfig {
name: string;
label: string;
validate: ValidationStrategy;
}
const formConfig: FieldConfig[] = [
{
name: 'email',
label: 'Email Adresi',
validate: composeValidations(required, email)
},
{
name: 'password',
label: 'Şifre',
validate: composeValidations(
required,
minLength(8),
maxLength(128)
)
},
{
name: 'phone',
label: 'Telefon Numarası',
validate: composeValidations(required, phone)
}
];
// Form component strategy'leri kullanır
function RegistrationForm() {
const [values, setValues] = useState<Record<string, string>>({});
const [errors, setErrors] = useState<Record<string, string>>({});
const handleChange = (name: string, value: string) => {
setValues(prev => ({ ...prev, [name]: value }));
// Strategy kullanarak validate et
const field = formConfig.find(f => f.name === name);
if (field) {
const error = field.validate(value);
setErrors(prev => ({ ...prev, [name]: error || '' }));
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Tüm field'ları validate et
const newErrors: Record<string, string> = {};
formConfig.forEach(field => {
const error = field.validate(values[field.name] || '');
if (error) newErrors[field.name] = error;
});
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
// Formu submit et
console.log('Form geçerli:', values);
};
return (
<form onSubmit={handleSubmit}>
{formConfig.map(field => (
<div key={field.name}>
<label>{field.label}</label>
<input
value={values[field.name] || ''}
onChange={(e) => handleChange(field.name, e.target.value)}
/>
{errors[field.name] && (
<span className="error">{errors[field.name]}</span>
)}
</div>
))}
<button type="submit">Kayıt Ol</button>
</form>
);
}
Bu yaklaşım sunar:
- Yeniden kullanılabilir validator’ler: Birden fazla formda kullan
- Composable: Strategy’leri kolayca birleştir
- Type-safe: TypeScript strategy imzalarını validate eder
- Test edilebilir: Validator’leri bağımsız test et
- Declarative: Konfigürasyon validation kurallarını açıklar
Strategy Pattern Ne Zaman Değer Katar
Strategy pattern kullan:
- Algoritma seçimi runtime’da olur
- Aynı interface’in birden fazla implementasyonu var
- Davranış konfigürasyona göre değişir
- Test için implementation değiştirmek gerekir
Strategy pattern kullanma:
- Sadece bir implementasyon var
- Mantık basit conditional (sadece
ifkullan) - Overhead faydayı aşar
- Function’lar kolayca abstract edilemez
Command Pattern: Redux Action’ları ve Undo/Redo
Command pattern, istekleri nesne olarak kapsüller, parameterization, queuing, logging ve undo operasyonlarını sağlar. Modern uygulamalarda Command pattern, Redux action’larında, undo/redo sistemlerinde ve task queue’lerinde görünür.
Klasik Command Pattern
Ders kitabı yaklaşımı execute() ve undo() methodlarıyla command nesneleri kullanır:
interface Command {
execute(): void;
undo(): void;
}
class TodoList {
private todos: string[] = [];
add(todo: string): void {
this.todos.push(todo);
}
remove(index: number): void {
this.todos.splice(index, 1);
}
getTodos(): string[] {
return [...this.todos];
}
}
class AddTodoCommand implements Command {
private index: number = -1;
constructor(
private todoList: TodoList,
private todo: string
) {}
execute(): void {
this.todoList.add(this.todo);
this.index = this.todoList.getTodos().length - 1;
}
undo(): void {
if (this.index !== -1) {
this.todoList.remove(this.index);
}
}
}
class RemoveTodoCommand implements Command {
private removedTodo: string = '';
private removedIndex: number = -1;
constructor(
private todoList: TodoList,
private index: number
) {}
execute(): void {
const todos = this.todoList.getTodos();
this.removedTodo = todos[this.index];
this.removedIndex = this.index;
this.todoList.remove(this.index);
}
undo(): void {
// Tam pozisyona geri yüklemek kolay değil
this.todoList.add(this.removedTodo);
}
}
// Undo/redo için command history
class CommandHistory {
private history: Command[] = [];
private current: number = -1;
execute(command: Command): void {
// Command'ı çalıştır
command.execute();
// Mevcut pozisyondan sonraki command'ları kaldır
this.history = this.history.slice(0, this.current + 1);
// Command'ı history'e ekle
this.history.push(command);
this.current++;
}
undo(): void {
if (this.canUndo()) {
this.history[this.current].undo();
this.current--;
}
}
redo(): void {
if (this.canRedo()) {
this.current++;
this.history[this.current].execute();
}
}
canUndo(): boolean {
return this.current >= 0;
}
canRedo(): boolean {
return this.current < this.history.length - 1;
}
}
// Kullanım
const todoList = new TodoList();
const history = new CommandHistory();
history.execute(new AddTodoCommand(todoList, 'Süt al'));
history.execute(new AddTodoCommand(todoList, 'Köpeği gezdir'));
console.log(todoList.getTodos()); // ['Süt al', 'Köpeği gezdir']
history.undo();
console.log(todoList.getTodos()); // ['Süt al']
history.redo();
console.log(todoList.getTodos()); // ['Süt al', 'Köpeği gezdir']
Command Olarak Redux Action’ları
Redux action’ları command nesneleridir. State değişikliklerini serializable data olarak kapsüllerler:
// Action type'ları (command type'ları)
interface AddTodoAction {
type: 'ADD_TODO';
payload: {
id: string;
text: string;
};
}
interface RemoveTodoAction {
type: 'REMOVE_TODO';
payload: {
id: string;
};
}
interface ToggleTodoAction {
type: 'TOGGLE_TODO';
payload: {
id: string;
};
}
type TodoAction = AddTodoAction | RemoveTodoAction | ToggleTodoAction;
// Action creator'lar (command factory'leri)
function addTodo(text: string): AddTodoAction {
return {
type: 'ADD_TODO',
payload: {
id: crypto.randomUUID(), // Not: crypto.randomUUID() HTTPS veya localhost gerektirir
text
}
};
}
function removeTodo(id: string): RemoveTodoAction {
return {
type: 'REMOVE_TODO',
payload: { id }
};
}
function toggleTodo(id: string): ToggleTodoAction {
return {
type: 'TOGGLE_TODO',
payload: { id }
};
}
// Reducer (command handler)
interface TodoState {
todos: Array<{ id: string; text: string; completed: boolean }>;
}
function todoReducer(
state: TodoState = { todos: [] },
action: TodoAction
): TodoState {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [
...state.todos,
{
id: action.payload.id,
text: action.payload.text,
completed: false
}
]
};
case 'REMOVE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload.id)
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload.id
? { ...todo, completed: !todo.completed }
: todo
)
};
default:
return state;
}
}
// Store (command executor)
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: todoReducer
});
// Command'ları dispatch et
store.dispatch(addTodo('Süt al'));
store.dispatch(addTodo('Köpeği gezdir'));
store.dispatch(toggleTodo(/* id */));
Redux command’ları sağlar:
- Serialization: Action’lar plain object’ler, log’lanabilir/store edilebilir
- Time-travel debugging: Action history’sini replay et
- Middleware: Command’ları intercept et ve transform et
- Undo/redo: Action history’sini store et, replay et veya tersine çevir
Redux Middleware: Command Pipeline
Middleware, reducer’lara ulaşmadan önce command’ları intercept eder:
import { Middleware } from 'redux';
// Logging middleware
const logger: Middleware = store => next => action => {
console.log('Dispatch ediliyor:', action);
const result = next(action);
console.log('Sonraki state:', store.getState());
return result;
};
// Analytics middleware
const analytics: Middleware = store => next => action => {
// Kullanıcı action'larını track et
if (action.type.startsWith('USER_')) {
trackEvent(action.type, action.payload);
}
return next(action);
};
// Error handling middleware
const errorHandler: Middleware = store => next => action => {
try {
return next(action);
} catch (error) {
console.error('Action hatası:', error);
store.dispatch({
type: 'ERROR_OCCURRED',
payload: { error: error.message }
});
}
};
// Redux Toolkit ile middleware uygula
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(logger, analytics, errorHandler)
});
Redux Thunk: Async Command’lar
Redux Thunk, side effect’li async command’ları sağlar:
import { ThunkAction } from 'redux-thunk';
import { AnyAction } from 'redux';
type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
AppState,
unknown,
AnyAction
>;
// Async command
const fetchUser = (userId: string): AppThunk => async (dispatch) => {
// Request command dispatch et
dispatch({
type: 'FETCH_USER_REQUEST',
payload: { userId }
});
try {
// Side effect gerçekleştir
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
// Success command dispatch et
dispatch({
type: 'FETCH_USER_SUCCESS',
payload: { user }
});
} catch (error) {
// Error command dispatch et
dispatch({
type: 'FETCH_USER_FAILURE',
payload: { error: error.message }
});
}
};
// Async command dispatch et
store.dispatch(fetchUser('123'));
Gerçek Senaryo: Undo/Redo’lu Rich Text Editor
Rich text editor, Command pattern’in undo/redo değerini gösterir:
interface EditorCommand {
execute(editor: Editor): void;
undo(editor: Editor): void;
description: string;
}
interface Editor {
content: string;
selectionStart: number;
selectionEnd: number;
insertText(position: number, text: string): void;
deleteText(position: number, length: number): string;
setSelection(start: number, end: number): void;
}
class InsertTextCommand implements EditorCommand {
description: string;
constructor(
private position: number,
private text: string
) {
this.description = `${position} pozisyonuna "${text}" ekle`;
}
execute(editor: Editor): void {
editor.insertText(this.position, this.text);
editor.setSelection(
this.position + this.text.length,
this.position + this.text.length
);
}
undo(editor: Editor): void {
editor.deleteText(this.position, this.text.length);
editor.setSelection(this.position, this.position);
}
}
class DeleteTextCommand implements EditorCommand {
private deletedText: string = '';
description: string;
constructor(
private position: number,
private length: number
) {
this.description = `${position} pozisyonundan ${length} karakter sil`;
}
execute(editor: Editor): void {
this.deletedText = editor.deleteText(this.position, this.length);
editor.setSelection(this.position, this.position);
}
undo(editor: Editor): void {
editor.insertText(this.position, this.deletedText);
editor.setSelection(
this.position + this.deletedText.length,
this.position + this.deletedText.length
);
}
}
class FormatTextCommand implements EditorCommand {
private previousFormat: string = '';
description: string;
constructor(
private start: number,
private end: number,
private format: 'bold' | 'italic' | 'underline'
) {
this.description = `${start}'dan ${end}'e ${format} uygula`;
}
execute(editor: Editor): void {
this.previousFormat = editor.getFormat(this.start, this.end);
editor.applyFormat(this.start, this.end, this.format);
}
undo(editor: Editor): void {
editor.applyFormat(this.start, this.end, this.previousFormat);
}
}
// Gruplama ile command history
class EditorHistory {
private history: EditorCommand[] = [];
private current: number = -1;
private groupedCommands: EditorCommand[] = [];
private isGrouping: boolean = false;
execute(command: EditorCommand, editor: Editor): void {
command.execute(editor);
if (this.isGrouping) {
this.groupedCommands.push(command);
return;
}
this.addToHistory(command);
}
private addToHistory(command: EditorCommand): void {
// Mevcut pozisyondan sonraki command'ları kaldır
this.history = this.history.slice(0, this.current + 1);
this.history.push(command);
this.current++;
}
beginGroup(): void {
this.isGrouping = true;
this.groupedCommands = [];
}
endGroup(): void {
this.isGrouping = false;
if (this.groupedCommands.length > 0) {
const group = new CompositeCommand(this.groupedCommands);
this.addToHistory(group);
}
}
undo(editor: Editor): void {
if (this.canUndo()) {
this.history[this.current].undo(editor);
this.current--;
}
}
redo(editor: Editor): void {
if (this.canRedo()) {
this.current++;
this.history[this.current].execute(editor);
}
}
canUndo(): boolean {
return this.current >= 0;
}
canRedo(): boolean {
return this.current < this.history.length - 1;
}
getHistory(): string[] {
return this.history.map(cmd => cmd.description);
}
}
// Gruplama için composite command
class CompositeCommand implements EditorCommand {
description: string;
constructor(private commands: EditorCommand[]) {
this.description = `${commands.length} command grubu`;
}
execute(editor: Editor): void {
this.commands.forEach(cmd => cmd.execute(editor));
}
undo(editor: Editor): void {
// Ters sırayla undo
for (let i = this.commands.length - 1; i >= 0; i--) {
this.commands[i].undo(editor);
}
}
}
// Editor component'inde kullanım
class RichTextEditor {
private history = new EditorHistory();
insertText(position: number, text: string): void {
const command = new InsertTextCommand(position, text);
this.history.execute(command, this.editor);
}
deleteText(position: number, length: number): void {
const command = new DeleteTextCommand(position, length);
this.history.execute(command, this.editor);
}
applyFormat(start: number, end: number, format: 'bold' | 'italic' | 'underline'): void {
const command = new FormatTextCommand(start, end, format);
this.history.execute(command, this.editor);
}
// Birden fazla command'ı tek undoable action olarak grupla
paste(text: string): void {
this.history.beginGroup();
// Varsa selection'ı sil
if (this.editor.selectionStart !== this.editor.selectionEnd) {
this.deleteText(
this.editor.selectionStart,
this.editor.selectionEnd - this.editor.selectionStart
);
}
// Yapıştırılan text'i ekle
this.insertText(this.editor.selectionStart, text);
this.history.endGroup();
}
undo(): void {
this.history.undo(this.editor);
}
redo(): void {
this.history.redo(this.editor);
}
}
Bu implementasyon sağlar:
- Detaylı undo: Her düzenleme operasyonu undo edilebilir
- Gruplu command’lar: Paste operasyonu tek action olarak undo edilir
- Command history: Tüm action’ların listesini görüntüle
- Redo desteği: Undo’daki hataları geri al
- Genişletilebilir: Yeni command type’ları kolayca ekle
Command Pattern Ne Zaman Değer Katar
Command pattern kullan:
- Undo/redo fonksiyonelliği gerekli
- Operasyonları queue’lemek veya schedule etmek gerekir
- Action’ları log’lamak/audit etmek gerekli
- Macro kaydetmek gerekli
- Transaction desteği gerekli
Command pattern kullanma:
- Undo olmadan basit CRUD operasyonları
- Action history gerekmez
- Overhead faydayı aşar
- Gerçek zamanlı collaboration (bunun yerine CRDT kullan)
State Pattern: Finite State Machine’ler
State pattern, nesnelerin internal state değiştiğinde davranışı değiştirmesini sağlar. UI geliştirmede State pattern, imkansız state kombinasyonlarını önler ve transition’ları açık hale getirir.
Problem: Boolean State Cehennemi
Karmaşık UI genellikle birden fazla boolean flag’e yol açar:
function Form() {
const [isValidating, setIsValidating] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [hasError, setHasError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
// Ya isSubmitting ve hasError ikisi de true ise?
// Ya isSuccess ve isValidating ikisi de true ise?
// Birçok imkansız kombinasyon mümkün
}
Boolean state’lerdeki problemler:
- İmkansız kombinasyonlar mümkün (
isSubmittingveisSuccessikisi de true) - Belirsiz transition’lar (validating’den submitting’e nasıl gideriz?)
- Test patlaması (tüm kombinasyonları test et)
- Davranış hakkında düşünmek zor
State Pattern Olarak Discriminated Union’lar
TypeScript discriminated union’ları imkansız state’leri önler:
type FormState =
| { status: 'idle' }
| { status: 'validating' }
| { status: 'submitting'; progress: number }
| { status: 'success'; data: SubmitResult }
| { status: 'error'; error: Error };
function Form() {
const [state, setState] = useState<FormState>({ status: 'idle' });
const handleSubmit = async () => {
// Validating'e geç
setState({ status: 'validating' });
try {
await validateForm();
// Submitting'e geç
setState({ status: 'submitting', progress: 0 });
const result = await submitForm((progress) => {
setState({ status: 'submitting', progress });
});
// Success'e geç
setState({ status: 'success', data: result });
} catch (error) {
// Error'a geç
setState({ status: 'error', error });
}
};
// State'e göre render et
switch (state.status) {
case 'idle':
return <FormFields onSubmit={handleSubmit} />;
case 'validating':
return <Spinner message="Doğrulanıyor..." />;
case 'submitting':
return <ProgressBar progress={state.progress} />;
case 'success':
return <SuccessMessage data={state.data} />;
case 'error':
return (
<>
<ErrorMessage error={state.error} />
<button onClick={handleSubmit}>Tekrar Dene</button>
</>
);
}
}
Discriminated union’ların faydaları:
- İmkansız state’ler imkansız: Aynı anda
validatingvesuccessolamazsın - Type-safe: TypeScript tüm state’lerin handle edildiğini garanti eder
- Açık transition’lar: Explicit state değişiklikleri
- İlişkili data: Her state ilgili dataya sahip (sadece submitting’deyken
progress)
XState: Explicit State Machine’ler
XState declarative state machine tanımı sağlar:
import { createMachine, interpret } from 'xstate';
const formMachine = createMachine({
id: 'form',
initial: 'idle',
context: {
formData: null,
error: null
},
states: {
idle: {
on: {
SUBMIT: 'validating'
}
},
validating: {
invoke: {
src: 'validateForm',
onDone: {
target: 'submitting'
},
onError: {
target: 'error',
actions: 'setError'
}
}
},
submitting: {
invoke: {
src: 'submitForm',
onDone: {
target: 'success',
actions: 'setFormData'
},
onError: {
target: 'error',
actions: 'setError'
}
}
},
success: {
type: 'final'
},
error: {
on: {
RETRY: 'validating',
CANCEL: 'idle'
}
}
}
}, {
actions: {
setFormData: (context, event) => {
context.formData = event.data;
},
setError: (context, event) => {
context.error = event.data;
}
},
services: {
validateForm: async () => {
// Validation mantığı
await new Promise(resolve => setTimeout(resolve, 1000));
},
submitForm: async () => {
// Submission mantığı
await new Promise(resolve => setTimeout(resolve, 2000));
return { success: true };
}
}
});
// React'te kullan
import { useMachine } from '@xstate/react';
function Form() {
const [state, send] = useMachine(formMachine);
return (
<form onSubmit={(e) => {
e.preventDefault();
send('SUBMIT');
}}>
{state.matches('idle') && (
<div>
<input name="email" />
<button type="submit">Gönder</button>
</div>
)}
{state.matches('validating') && (
<Spinner message="Form doğrulanıyor..." />
)}
{state.matches('submitting') && (
<Spinner message="Gönderiliyor..." />
)}
{state.matches('success') && (
<SuccessMessage data={state.context.formData} />
)}
{state.matches('error') && (
<div>
<ErrorMessage error={state.context.error} />
<button onClick={() => send('RETRY')}>Tekrar Dene</button>
<button onClick={() => send('CANCEL')}>İptal</button>
</div>
)}
</form>
);
}
State Machine’leri Görselleştirmek
XState machine’leri Mermaid diagram’larıyla görselleştirilebilir:
XState avantajları:
- Görsel tasarım: State machine’i görsel olarak tasarla, kod oluştur
- İmkansız transition’lar önlendi:
idle’dansuccess’e gidemezsin - Test oluşturma: State machine’den test’ler oluştur
- Actor model: State machine’ler spawn edebilir ve iletişim kurabilir
- History state’leri: Geri dönerken önceki state’i hatırla
Gerçek Senaryo: Multi-Step Wizard
Multi-step form’lar explicit state machine’lerden faydalanır:
import { createMachine, assign } from 'xstate';
interface WizardContext {
step1Data: any;
step2Data: any;
step3Data: any;
error: string | null;
}
type WizardEvent =
| { type: 'NEXT'; data: any }
| { type: 'PREVIOUS' }
| { type: 'SUBMIT' }
| { type: 'RESET' };
const wizardMachine = createMachine<WizardContext, WizardEvent>({
id: 'wizard',
initial: 'step1',
context: {
step1Data: null,
step2Data: null,
step3Data: null,
error: null
},
states: {
step1: {
on: {
NEXT: {
target: 'step2',
actions: assign({
step1Data: (context, event) => event.data
})
}
}
},
step2: {
on: {
NEXT: {
target: 'step3',
actions: assign({
step2Data: (context, event) => event.data
})
},
PREVIOUS: 'step1'
}
},
step3: {
on: {
PREVIOUS: 'step2',
SUBMIT: 'submitting'
}
},
submitting: {
invoke: {
src: 'submitWizard',
onDone: 'success',
onError: {
target: 'step3',
actions: assign({
error: (context, event) => event.data.message
})
}
}
},
success: {
type: 'final'
}
}
}, {
services: {
submitWizard: async (context) => {
const response = await fetch('/api/wizard', {
method: 'POST',
body: JSON.stringify({
step1: context.step1Data,
step2: context.step2Data,
step3: context.step3Data
})
});
if (!response.ok) {
throw new Error('Gönderim başarısız');
}
return response.json();
}
}
});
// React component
function Wizard() {
const [state, send] = useMachine(wizardMachine);
const handleNext = (data: any) => {
send({ type: 'NEXT', data });
};
const handlePrevious = () => {
send('PREVIOUS');
};
const handleSubmit = () => {
send('SUBMIT');
};
return (
<div>
{state.matches('step1') && (
<Step1 onNext={handleNext} />
)}
{state.matches('step2') && (
<Step2
initialData={state.context.step1Data}
onNext={handleNext}
onPrevious={handlePrevious}
/>
)}
{state.matches('step3') && (
<Step3
data={{
step1: state.context.step1Data,
step2: state.context.step2Data
}}
error={state.context.error}
onSubmit={handleSubmit}
onPrevious={handlePrevious}
/>
)}
{state.matches('submitting') && (
<Spinner message="Wizard gönderiliyor..." />
)}
{state.matches('success') && (
<SuccessMessage message="Wizard tamamlandı!" />
)}
</div>
);
}
State Machine’leri Ne Zaman Kullanmalı
State machine’leri kullan:
- Birçok state’li karmaşık UI workflow’ları
- İmkansız state’leri önlemek gerekli
- State transition’larının business rule’ları var
- Ekip iletişimi için görsel tasarım yararlı
- State transition’larını test etmek kritik
State machine’leri kullanma:
- 2-3 state’li basit form’lar
- Overhead faydayı aşar
- Ekip state machine’lere aşina değil
- Discriminated union’lar yeterli
Mediator Pattern: Component’leri Ayırma
Mediator pattern, nesneler arasındaki karmaşık iletişimi merkezileştirir, direkt referansları önler ve coupling’i azaltır. Modern uygulamalarda Mediator, event bus’ları, React Context ve state management library’leri olarak görünür.
Problem: Tight Coupling
Mediator olmadan component’ler birbirlerine direkt referans verir:
class UserList {
constructor(private userDetails: UserDetails) {}
selectUser(user: User): void {
// UserDetails'e direkt bağımlılık
this.userDetails.display(user);
}
}
class UserDetails {
display(user: User): void {
// Kullanıcı detaylarını göster
}
}
// UserList'i UserDetails olmadan test etmek zor
// UserList'i farklı detail component ile yeniden kullanmak zor
Mediator Olarak Event Bus
Event bus component’leri ayırır:
type EventCallback = (data: any) => void;
class EventBus {
private events = new Map<string, EventCallback[]>();
subscribe(event: string, callback: EventCallback): () => void {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event)!.push(callback);
// Unsubscribe fonksiyonunu döndür
return () => {
const callbacks = this.events.get(event);
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
}
};
}
publish(event: string, data?: any): void {
const callbacks = this.events.get(event);
if (callbacks) {
callbacks.forEach(callback => callback(data));
}
}
clear(): void {
this.events.clear();
}
}
// Component'ler mediator aracılığıyla iletişim kurar
class UserList {
constructor(private eventBus: EventBus) {}
selectUser(user: User): void {
// Direkt çağrı yerine event publish et
this.eventBus.publish('user:selected', user);
}
}
class UserDetails {
constructor(private eventBus: EventBus) {
// Event'lere subscribe ol
this.eventBus.subscribe('user:selected', this.display.bind(this));
}
display(user: User): void {
console.log('Kullanıcı gösteriliyor:', user);
}
}
class UserStats {
constructor(private eventBus: EventBus) {
this.eventBus.subscribe('user:selected', this.loadStats.bind(this));
}
loadStats(user: User): void {
console.log('İstatistikler yükleniyor:', user);
}
}
// Kullanım
const eventBus = new EventBus();
const userList = new UserList(eventBus);
const userDetails = new UserDetails(eventBus);
const userStats = new UserStats(eventBus);
// Component'ler birbirlerini bilmez
userList.selectUser({ id: '1', name: 'John' });
Mediator Olarak React Context
React Context, component ağaçları için mediator pattern sağlar:
interface AppContextValue {
selectedUser: User | null;
setSelectedUser: (user: User | null) => void;
notifications: Notification[];
addNotification: (notification: Notification) => void;
}
const AppContext = createContext<AppContextValue>(null!);
function AppProvider({ children }: { children: ReactNode }) {
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [notifications, setNotifications] = useState<Notification[]>([]);
const addNotification = (notification: Notification) => {
setNotifications(prev => [...prev, notification]);
};
return (
<AppContext.Provider value={{
selectedUser,
setSelectedUser,
notifications,
addNotification
}}>
{children}
</AppContext.Provider>
);
}
// Component'ler context aracılığıyla iletişim kurar
function UserList() {
const { setSelectedUser } = useContext(AppContext);
const users = useUsers();
return (
<ul>
{users.map(user => (
<li key={user.id} onClick={() => setSelectedUser(user)}>
{user.name}
</li>
))}
</ul>
);
}
function UserDetails() {
const { selectedUser } = useContext(AppContext);
if (!selectedUser) {
return <div>Bir kullanıcı seç</div>;
}
return (
<div>
<h2>{selectedUser.name}</h2>
<p>{selectedUser.email}</p>
</div>
);
}
function UserStats() {
const { selectedUser } = useContext(AppContext);
if (!selectedUser) return null;
return <div>{selectedUser.name} için istatistikler</div>;
}
// App yapısı
function App() {
return (
<AppProvider>
<UserList />
<UserDetails />
<UserStats />
</AppProvider>
);
}
Global Mediator Olarak Redux
Redux tüm uygulama iletişimini action’lar aracılığıyla merkezileştirir:
// Tüm component'ler store aracılığıyla iletişim kurar
function UserList() {
const dispatch = useDispatch();
const users = useSelector(state => state.users);
const handleSelect = (user: User) => {
// Component'ler birbirlerini direkt çağırmaz
dispatch(selectUser(user));
dispatch(fetchUserDetails(user.id));
dispatch(loadUserStats(user.id));
};
return (
<ul>
{users.map(user => (
<li key={user.id} onClick={() => handleSelect(user)}>
{user.name}
</li>
))}
</ul>
);
}
function UserDetails() {
const selectedUser = useSelector(state => state.selectedUser);
const userDetails = useSelector(state => state.userDetails);
if (!selectedUser) return null;
return (
<div>
<h2>{selectedUser.name}</h2>
{userDetails && <DetailedInfo details={userDetails} />}
</div>
);
}
function UserStats() {
const userStats = useSelector(state => state.userStats);
if (!userStats) return null;
return <StatsDisplay stats={userStats} />;
}
Mediator Pattern’i Ne Zaman Kullanmalı
Event bus kullan:
- Aynı modüldeki component’ler iletişim kurmalı
- Hafif pub/sub gerekli
- Global state gereksinimleri yok
- Hızlı prototip veya basit app
React Context kullan:
- Aynı subtree’deki component’ler iletişim kurar
- Prop drilling acı verici hale gelir
- Tema, auth veya locale paylaşımı
- Orta ölçekli component ağaçları
Redux kullan:
- Karmaşık state’li büyük uygulama
- Birçok component aynı state’e erişir
- DevTools ve time-travel gerekli
- Middleware değerli (saga’lar, thunk’lar)
- Ekip Redux ile deneyimli
Mediator’dan kaçın:
- Direkt iletişim daha basit (parent-child prop’lar)
- Sadece 2-3 component dahil
- Gereksiz indirection ekleniyor
- Debugging zorlaşır
Önemli Çıkarımlar
-
Observer reaktif programlamaya evrildi: RxJS, Redux ve React hook’ları, Observer pattern’in daha iyi composition, error handling ve backpressure yönetimiyle evrimini temsil eder.
-
Strategy pattern class’lar yerine function’lar kullanır: JavaScript’in first-class function’ları Strategy pattern’i basitleştirir - strategy class’ları oluşturmak yerine function’ları strategy olarak geçir.
-
Command Redux’u ve undo/redo’yu güçlendirir: Redux action’ları Command pattern’dir. Pattern undo/redo, action logging ve operation queuing için değerlidir.
-
State machine’ler imkansız state’leri önler: XState ve discriminated union’lar, boolean state cehennemini önlemek ve transition’ları açık hale getirmek için State pattern uygular.
-
Mediator coupling’i azaltır: Event bus’ları, React Context ve Redux, farklı ölçeklerde Mediator pattern uygular - uygulama boyutu ve iletişim karmaşıklığına göre seç.
-
Context önemli: Bu kalıplar evrensel olarak iyi değil. RxJS karmaşık async koordinasyon için değer katar ama basit event’ler için aşırı. State machine’ler karmaşık workflow’lara yardımcı olur ama basit form’ları karmaşıklaştırır. Gerçek ihtiyaçlara göre seç.
-
Modern implementasyonlar daha temiz: Bugünün davranışsal kalıpları, klasik GoF versiyonlarının C++/Smalltalk’ta gerektirdiğinden daha temiz implementasyonlar için dil özelliklerinden (first-class function’lar, closure’lar, modüller) yararlanır.
İlgili Yazılar
- Modern TypeScript’te Creational Pattern’ler - Bu serinin 1. bölümü
- Component Composition ile Structural Pattern’ler - Bu serinin 2. bölümü
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.
Serideki tüm yazılar
İlgili yazılar
SOLID prensiplerininin modern JavaScript geliştirmede nasıl uygulanacağını öğrenin. TypeScript, React hooks ve fonksiyonel pattern'ler ile pratik örnekler - ayrıca ne zaman kullanmalı, ne zaman gereksiz.
Kural tabanlı chatbot'lardan otonom AI agent'larına mimari evrimi keşfet. ReAct, Plan-and-Execute ve çoklu-agent desenleri TypeScript implementasyonları ve pratik geçiş stratejileriyle öğren.
Singleton, Factory, Builder ve Prototype pattern'lerinin TypeScript'te nasıl evrildiğini keşfet. ES modüllerinin singleton'ları ne zaman değiştirdiğini, factory function'ların ne zaman class'lardan daha iyi olduğunu ve TypeScript'in type sisteminin oyunu nasıl değiştirdiğini öğren.
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.
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.