React ile Strategy Pattern Kullanımı

R

Frontend tarafında karmaşıklaşan iş mantıkları için yazılım tasarım desenleri, okunabilir ve genişletilebilir projeler üretmenin temel taşıdır. Bu yazıda, Strategy Pattern’in modern React ve TypeScript ile nasıl uygulanacağını inceleyeceğiz.

Strategy Pattern Nedir?

Gelişen yazılım projelerinde zamanla birçok if/else veya switch-case bloklarıyla karşılaşırız. Özellikle farklı iş kurallarına göre farklı davranışların gerektiği durumlarda, bu yapılar kodun okunabilirliğini ve sürdürülebilirliğini zorlaştırır. Hem SOLID prensiplerine aykırı hale gelir, hem de SonarQube gibi statik analiz araçlarında “too many conditions” gibi kod kalitesi uyarılarıyla karşılaşmamıza sebep olur.

İşte bu gibi durumlarda Strategy Pattern, farklı davranışları soyutlayarak merkezi bir kontrol noktası oluşturur. Ortak bir arayüz üzerinden davranışları yönetmemizi sağlar. Yeni bir strateji eklemek için mevcut kodu değiştirmek gerekmez; yalnızca yeni bir strateji implementasyonu oluşturmak yeterlidir. Bu da bizi Open/Closed Principle (Açık/Kapalı Prensibi) ile uyumlu hale getirir.

Uygulama Yapısı ve Örnek Senaryomuz

Bu yazıda Strategy Pattern’i bir yemek sipariş uygulamasındaki ödeme sistemi üzerinden örnekleyeceğiz. Ancak unutulmamalı ki, bu yapı birçok farklı senaryoya kolaylıkla uyarlanabilir.

Yemek sipariş uygulamamızda üç farklı ödeme yöntemi bulunuyor:

  • Kredi Kartı (Credit Card)
  • Yemek Kartı (Meal Card)
  • Nakit (Cash)

Her bir ödeme tipi farklı UI davranışlarına ve kullanıcı girdilerine sahiptir. Bunu Strategy Pattern ile soyutlayarak sade bir yapı kuracağız.

src/
├── strategies/
│   ├── CreditCardStrategy.tsx
│   ├── MealCardStrategy.tsx
│   ├── CashStrategy.tsx
├── PaymentSelector.tsx
├── types.ts
└── App.tsx

Şimdi type.ts dosyamızın içeriğini dolduralım. Tüm ödeme stratejileri (Kredi Kartı, Yemek Kartı, Nakit) aynı türden verilerle çalışır: ödeme tutarı (amount) ve ödeme işlemi başarıyla tamamlandığında çalışacak bir callback fonksiyonu (onSuccess). Her strateji component’inin bu iki parametreyle çalıştığından emin olmak için ortak bir interface tanımlıyoruz.

Bu sayede hem tip güvenliğini sağlıyoruz, hem de Strategy Pattern’in temelindeki değiştirilebilir davranış ilkesini uygulamış oluyoruz: farklı component’ler aynı interface’i uyguladığı sürece, onları tek bir noktadan yönetebiliriz.

export interface PaymentProps {
  amount: number;
  onSuccess: () => void;
}

Şimdi strategies altındaki yapılarımızı oluşturabiliriz. “strategies” klasörü Strategy Pattern’in temelini bu componentler oluşturur. Her ödeme yöntemi, kendi özel davranışını bir component olarak temsil eder. Bu componentler, ortak bir PaymentProps interface’ini uygular. Böylece, PaymentSelector component’i bu stratejilerin hepsini aynı biçimde kullanabilir.

CreditCardStrategy.tsx – Kredi Kartı ile Ödeme

Bu component kullanıcıdan bir kredi kartı numarası girmesini ister. cardNumber state ile input kontrolü yapılır. Ödeme butonuna tıklanıldığında console’a bilgi yazılır ve onSuccess() callback’i tetiklenerek ödeme işleminin başarıyla tamamlandığı simüle edilir. Gerçek hayatta burada ödeme API’si ile entegrasyon yapılması gerekir.

import React, { useState } from "react";
import { PaymentProps } from "../types";

const CreditCardStrategy: React.FC<PaymentProps> = ({ amount, onSuccess }) => {
  const [cardNumber, setCardNumber] = useState("");

  const handlePay = () => {
    console.log("Kredi kartı ile ödeme alındı:", cardNumber);
    onSuccess();
  };

  return (
    <div>
      <h3>Kredi Kartı ile Ödeme: {amount}₺</h3>
      <input
        type="text"
        placeholder="Kart Numarası"
        value={cardNumber}
        onChange={(e) => setCardNumber(e.target.value)}
      />
      <button onClick={handlePay}>Öde</button>
    </div>
  );
};

export default CreditCardStrategy;

MealCardStrategy.tsx – Yemek Kartı ile Ödeme

Bu strateji, kullanıcıdan ekstra bir bilgi istemeden tek tıklamayla ödeme alındığı bir senaryoyu temsil eder. Örneğin arka planda entegre bir yemek kartı cüzdanı olabilir. handlePay() fonksiyonu ödeme işlemini gerçekleştirir ve ardından onSuccess() ile işlemin tamamlandığını bildirir.

import React from "react";
import { PaymentProps } from "../types";

const MealCardStrategy: React.FC<PaymentProps> = ({ amount, onSuccess }) => {
  const handlePay = () => {
    console.log("Yemek kartı ile ödeme alındı");
    onSuccess();
  };

  return (
    <div>
      <h3>Yemek Kartı ile Ödeme: {amount}₺</h3>
      <button onClick={handlePay}>Öde</button>
    </div>
  );
};

export default MealCardStrategy;

CashStrategy.tsx – Nakit ile Ödeme

Bu component, kullanıcıya ödeme sırasında kurye’ye iletmek istediği bir not yazma imkanı sunar. Örneğin “para üstü 50 TL olsun” gibi. Not note state’iyle kontrol edilir. Ödeme tamamlandığında not console’a yazdırılır ve onSuccess() fonksiyonu çağrılarak süreç tamamlanır.

import React, { useState } from "react";
import { PaymentProps } from "../types";

const CashStrategy: React.FC<PaymentProps> = ({ amount, onSuccess }) => {
  const [note, setNote] = useState("");

  const handlePay = () => {
    console.log("Nakit ödeme alınacak. Not:", note);
    onSuccess();
  };

  return (
    <div>
      <h3>Nakit Ödeme: {amount}₺</h3>
      <textarea
        placeholder="Sürücüye not"
        value={note}
        onChange={(e) => setNote(e.target.value)}
      />
      <button onClick={handlePay}>Öde</button>
    </div>
  );
};

export default CashStrategy;

PaymentSelector.tsx – Ödeme Yöntemine Göre Gösterim

Bu component seçilen ödeme yöntemine göre ilgili strateji component’ini belirler. Buradaki strategies map’i sayesinde if/else ya da switch kullanmadan, dinamik olarak component seçimi yapılır. Böylece kod hem sade hem de genişlemeye açık hale gelir. Yeni bir ödeme yöntemi eklemek istediğinizde sadece ilgili component’i tanımlayıp bu map’e eklemeniz yeterli olur.

import CreditCardStrategy from "./strategies/CreditCardStrategy";
import MealCardStrategy from "./strategies/MealCardStrategy";
import CashStrategy from "./strategies/CashStrategy";
import { PaymentProps } from "./types";

export enum PaymentMethod {
  CREDIT_CARD = "CREDIT_CARD",
  MEAL_CARD = "MEAL_CARD",
  CASH = "CASH",
}

interface Props extends PaymentProps {
  method: PaymentMethod;
}

const PaymentSelector: React.FC<Props> = ({ method, amount, onSuccess }) => {
  const strategies = {
    [PaymentMethod.CREDIT_CARD]: CreditCardStrategy,
    [PaymentMethod.MEAL_CARD]: MealCardStrategy,
    [PaymentMethod.CASH]: CashStrategy,
  };

  const SelectedComponent = strategies[method];
  return <SelectedComponent amount={amount} onSuccess={onSuccess} />;
};

export default PaymentSelector;

App.tsx – Ödeme Yöntemi Seçimi

App component’i yani uygulamamızın ana component’i ödeme yöntemini kullanıcıdan alır ve seçilen stratejiye uygun component’i PaymentSelector üzerinden render eder. Burada yapılan seçim sayesinde sistem tamamen dinamik hale gelir. Component yapısı dışarıdan yönetildiği için test etmek, varyantları kontrol etmek veya yeni özellikler eklemek oldukça kolaydır.

import React, { useState } from "react";
import PaymentSelector, { PaymentMethod } from "./PaymentSelector";

function App() {
  const [method, setMethod] = useState<PaymentMethod>(PaymentMethod.CREDIT_CARD);

  const handleSuccess = () => {
    alert("Ödeme başarılı!");
  };

  return (
    <div>
      <h2>Ödeme Yöntemini Seç</h2>
      <select onChange={(e) => setMethod(e.target.value as PaymentMethod)}>
        <option value={PaymentMethod.CREDIT_CARD}>Kredi Kartı</option>
        <option value={PaymentMethod.MEAL_CARD}>Yemek Kartı</option>
        <option value={PaymentMethod.CASH}>Nakit</option>
      </select>

      <hr />

      <PaymentSelector method={method} amount={120} onSuccess={handleSuccess} />
    </div>
  );
}

export default App;


Sonuç olarak React ve TypeScript kullanarak Strategy Pattern’in nasıl uygulanabileceğini gerçek bir senaryo üzerinden ele aldık. Farklı ödeme yöntemlerini temsil eden component’leri ayrı stratejiler olarak tanımlayarak, merkezi bir seçim noktası üzerinden bu davranışları yönettik. Böylece hem if-else veya switch-case gibi karmaşık kontrol bloklarından kurtulmuş olduk hem de her stratejiyi kendi içinde bağımsız ve sade şekilde yönetebildik.

Yorum yazın

Sinan BOZKUŞ

Get in touch

Quickly communicate covalent niche markets for maintainable sources. Collaboratively harness resource sucking experiences whereas cost effective meta-services.