Skip to content
~/sph.sh

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:

typescript
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ımclass 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:

typescript
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:

typescript
import { Observable, Subject, of } from 'rxjs';import {  debounceTime,  distinctUntilChanged,  map,  filter,  catchError} from 'rxjs';
// Reaktif operator'lerle search inputconst 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 etsearchInput$.next('rea');searchInput$.next('react');searchInput$.next('reactive');

RxJS'i güçlü yapan:

  1. Composable operator'ler: Transformation'ları declarative olarak zincirle
  2. Error handling: Error'lar stream boyunca yayılır
  3. Completion signal'ları: Stream ne zaman bittiğini bil
  4. Backpressure: Hızlı producer'ları throttle gibi operator'lerle handle et
  5. Cancellation: Unsubscribe execution'ı durdurur
  6. 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:

typescript
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 WebSocketfunction 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üncellestockPrices$.pipe(  filter(price => price.symbol === 'AAPL'),  map(price => price.value)).subscribe(value => {  updateChart('AAPL', value);});
// Observer 2: Portfolio toplamını hesaplastockPrices$.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 alertstockPrices$.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 cleanupconst unsubscribe$ = new Subject<void>();stockPrices$.pipe(  takeUntil(unsubscribe$)).subscribe();
// Sonra: tüm subscription'ları temizlefunction 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: takeUntil memory leak olmamasını sağlar

Redux: State için Observer Pattern

Redux, uygulama state yönetimi için Observer pattern uygular:

typescript
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 etstore.dispatch(increment());store.dispatch(setUser({ id: '1', name: 'John' }));
// Sonra: cleanupunsubscribe();

React-Redux component'leri observer olarak bağlar:

Note: Modern React-Redux, connect HOC yerine hook'ları (useSelector/useDispatch) tercih eder. Aşağıdaki örnek eğitim amaçlı eski pattern'i gösterir.

typescript
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 olurexport 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:

typescript
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özlerfunction 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:

typescript
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ırclass PaymentProcessor {  constructor(private strategy: PaymentStrategy) {}
  setStrategy(strategy: PaymentStrategy): void {    this.strategy = strategy;  }
  async processPayment(amount: number): Promise<void> {    await this.strategy.pay(amount);  }}
// Kullanımconst 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:

typescript
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 ederclass 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 gerekmezasync function processPayment(  amount: number,  strategy: PaymentStrategy): Promise<void> {  await strategy(amount);}
// Kullanımawait 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:

typescript
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ımconst 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:

typescript
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'leriinterface 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:

typescript
type ValidationStrategy = (value: string) => string | null;
// Tekil validation strategy'lericonst 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 etfunction 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ırfunction 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 if kullan)
  • 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:

typescript
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 historyclass 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ımconst 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:

typescript
// 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 etstore.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:

typescript
import { Middleware } from 'redux';
// Logging middlewareconst logger: Middleware = store => next => action => {  console.log('Dispatch ediliyor:', action);  const result = next(action);  console.log('Sonraki state:', store.getState());  return result;};
// Analytics middlewareconst 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 middlewareconst 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 uygulaimport { 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:

typescript
import { ThunkAction } from 'redux-thunk';import { AnyAction } from 'redux';
type AppThunk<ReturnType = void> = ThunkAction<  ReturnType,  AppState,  unknown,  AnyAction>;
// Async commandconst 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 etstore.dispatch(fetchUser('123'));

Gerçek Senaryo: Undo/Redo'lu Rich Text Editor

Rich text editor, Command pattern'in undo/redo değerini gösterir:

typescript
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 historyclass 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 commandclass 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ımclass 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:

typescript
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 (isSubmitting ve isSuccess ikisi 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:

typescript
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 validating ve success olamazsı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:

typescript
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 kullanimport { 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'dan success'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:

typescript
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 componentfunction 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:

typescript
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:

typescript
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 kurarclass 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ımconst eventBus = new EventBus();const userList = new UserList(eventBus);const userDetails = new UserDetails(eventBus);const userStats = new UserStats(eventBus);
// Component'ler birbirlerini bilmezuserList.selectUser({ id: '1', name: 'John' });

Mediator Olarak React Context

React Context, component ağaçları için mediator pattern sağlar:

typescript
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 kurarfunction 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:

typescript
// Tüm component'ler store aracılığıyla iletişim kurarfunction 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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ç.

  6. 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ç.

  7. 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

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.

İlerleme3/4 yazı tamamlandı

İlgili Yazılar