İçeriğe atla

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:

  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:

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: takeUntil memory 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, connect HOC 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 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:

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

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:

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:

SUBMIT

Validation Success

Validation Failed

Submit Success

Submit Failed

RETRY

CANCEL

Idle

Validating

Submitting

Error

Success

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:

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

  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.

İlerleme 3 / 4 yazı

İlgili yazılar

JavaScript'te SOLID Prensipleri: TypeScript ve React ile Pratik Rehber

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.

typescriptjavascriptreact+5
Chatbot'lardan Otonom Agent'lara: Mimari Desenler

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.

ai-agentschatbotsarchitecture+4
Modern TypeScript'te Creational Pattern'lerin Evrimi

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.

typescriptdesign-patternssoftware-architecture+5
Gang of Four'un Ötesindeki Tasarım Kalıpları

JavaScript ve TypeScript ekosistemlerinden doğan modern kalıpları keşfedelim - hooks, compound components, render props ve GoF'un hiç karşılaşmadığı problemleri çözen repository pattern'leri.

typescriptreactdesign-patterns+5
Yapısal Patternler ve Component Composition

Decorator, Adapter, Facade, Composite ve Proxy patternlerinin React ve TypeScript'te nasıl evrildiğini keşfedin. HOC'ların ne zaman hook'lara yol verdiğini, adapterlerin third-party API'ları nasıl izole ettiğini ve facade'ların karmaşıklığı nasıl basitleştirdiğini öğrenin.

typescriptreactdesign-patterns+6