Strategy Kalıbı
Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 95 / 104.
Strateji deseni (Strategy pattern), bir algoritma ailesi tanımlar, her birini kapsüller ve onları birbirinin yerine kullanılabilir hale getirir. Bu, bir nesnenin davranışını kodunu değiştirmeden çalışma zamanında değiştirmenize olanak tanır; algoritma, onu kullanan istemcilerden bağımsız olarak değişir.
Desen üç parçadan oluşur: algoritma metodunu bildiren bir Strateji arayüzü, farklı varyasyonları uygulayan Somut Stratejiler ve bir strateji kullanan bir Bağlam:
#include <iostream>
#include <memory>
// Strategy arayüzü
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
// Somut stratejiler
class CreditCardPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via Credit Card\n";
}
};
class PayPalPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via PayPal\n";
}
};
// Bağlam (Context)
class ShoppingCart {
std::unique_ptr<PaymentStrategy> strategy;
public:
void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
strategy = std::move(s);
}
void checkout(int total) {
if (strategy) strategy->pay(total);
}
};
int main() {
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);
}ShoppingCart hangi ödeme yöntemini kullandığını bilmez; sadece ayarlanmış olan strateji neyse onun üzerinde pay() metodunu çağırır. setPaymentMethod() ile çalışma zamanında stratejileri değiştirebilirsiniz, bu da sistemi esnek ve yeni ödeme seçenekleriyle genişletilmesi kolay hale getirir.
Belirli bir görev için birden fazla algoritmanız olduğunda ve bunlar arasında dinamik olarak geçiş yapmak istediğinizde veya davranış seçimi için koşullu ifadelerden kaçınmak istediğinizde Strategy desenini kullanın.
Görev
KolayFarklı gönderim yöntemlerine göre teslimat maliyetlerini hesaplamak için Strateji (Strategy) desenini kullanan bir Gönderim Hesaplayıcı oluşturalım. Bu, algoritmaları çalışma zamanında değiştirmeniz gereken pratik bir senaryodur; aynı paket, her biri kendi fiyatlandırma mantığına sahip olan kara, hava veya ekspres yoluyla gönderilebilir.
Kodunuzu üç dosya halinde düzenleyeceksiniz:
ShippingStrategy.h: Strateji arayüzünüzü ve somut gönderim stratejilerini tanımlayın.Gönderim maliyetini double olarak döndüren saf sanal bir
calculateCost(double weight)metoduna ve sanal bir yıkıcıya (destructor) sahip soyut birShippingStrategysınıfı oluşturun.Ardından üç somut strateji uygulayın:
GroundShipping— ağırlık birimi başına1.5maliyet (weight * 1.5)AirShipping— ağırlık birimi başına4.0maliyet (weight * 4.0)ExpressShipping— ağırlık birimi başına6.5maliyet artı10.0sabit ücret (weight * 6.5 + 10.0)
ShippingService.h: Bir gönderim stratejisi kullanan bağlam (context) sınıfını oluşturun.ShippingServicesınıfınız, özel bir üye olarak birstd::unique_ptr<ShippingStrategy>tutmalıdır. Şunları uygulayın:- Gönderim yöntemini değiştirmek için bir
setStrategy(std::unique_ptr<ShippingStrategy> strategy)metodu - Maliyeti hesaplamak ve döndürmek için mevcut stratejiyi kullanan bir
calculateShipping(double weight)metodu
calculateShippingçağrıldığında herhangi bir strateji ayarlanmamışsa0.0döndürün.- Gönderim yöntemini değiştirmek için bir
main.cpp: Çalışma zamanında strateji değiştirmeyi gösterin.İki girdi okuyun:
- Paket ağırlığı (double)
- Gönderim yöntemi:
ground,airveyaexpress
Bir
ShippingServiceoluşturun ve girdi yöntemine göre uygun stratejiyi ayarlayın. Gönderim maliyetini hesaplayın ve yazdırın.Ardından farklı bir stratejiye geçin (girdi
airdeğilseairkullanın, aksi takdirdegroundkullanın) ve aynı ağırlık için maliyeti tekrar hesaplayın. Bu, çalışma zamanında strateji değiştirmenin gücünü gösterir.Her maliyeti, yöntem adıyla ön eklenmiş olarak tam olarak bir ondalık basamakla kendi satırına yazdırın:
[Method]: $[cost]
Örneğin, 5.0 ve ground girdileriyle:
Ground: $7.5
Air: $20.03.0 ve express girdileriyle:
Express: $29.5
Air: $12.010.0 ve air girdileriyle:
Air: $40.0
Ground: $15.0ShippingService sınıfının her bir fiyatlandırma algoritmasının ayrıntılarını bilmesine nasıl gerek kalmadığına dikkat edin; sadece o anda ayarlanmış olan stratejiye işi devreder. Servis sınıfını hiç değiştirmeden kolayca yeni gönderim yöntemleri (drone teslimatı veya aynı gün teslimat gibi) ekleyebilirsiniz.
Kopya kağıdı
Strateji deseni (Strategy pattern), bir algoritma ailesi tanımlar, her birini kapsüller ve onları birbirinin yerine kullanılabilir hale getirir. Bu, bir nesnenin davranışının, kodunu değiştirmeden çalışma zamanında değiştirilmesine olanak tanır.
Desen üç bölümden oluşur:
- Strateji arayüzü (Strategy interface): Algoritma metodunu bildiren soyut bir sınıf
- Somut Stratejiler (Concrete Strategies): Farklı algoritma varyasyonlarını uygulayan sınıflar
- Bağlam (Context): Bir stratejiyi kullanan ve bunlar arasında geçiş yapabilen bir sınıf
// Strategy interface
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
// Concrete strategies
class CreditCardPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via Credit Card\n";
}
};
class PayPalPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via PayPal\n";
}
};
// Context
class ShoppingCart {
std::unique_ptr<PaymentStrategy> strategy;
public:
void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
strategy = std::move(s);
}
void checkout(int total) {
if (strategy) strategy->pay(total);
}
};Bağlam, hangi somut stratejiyi kullandığını bilmez; sadece arayüz metodunu çağırır. Stratejiler, bir ayarlayıcı (setter) metodu kullanılarak çalışma zamanında değiştirilebilir:
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);Ne zaman kullanılır: Belirli bir görev için birden fazla algoritmanız olduğunda ve bunlar arasında dinamik olarak geçiş yapmak istediğinizde veya davranışı seçmek için koşullu ifadelerden (if-else, switch) kaçınmak istediğinizde Strateji desenini kullanın.
Kendin dene
#include <iostream>
#include <string>
#include <iomanip>
#include <memory>
#include "ShippingStrategy.h"
#include "ShippingService.h"
int main() {
double weight;
std::string method;
std::cin >> weight;
std::cin >> method;
// Çıktıyı 1 ondalık basamağa ayarla
std::cout << std::fixed << std::setprecision(1);
ShippingService service;
// TODO: Giriş yöntemine göre ("ground", "air" veya "express"):
// 1. Servis üzerinde uygun stratejiyi ayarla
// 2. Maliyeti şu formatta hesapla ve yazdır: "[Method]: $[cost]"
// TODO: Farklı bir stratejiye geç:
// - Eğer giriş "air" ise, GroundShipping stratejisine geç
// - Aksi takdirde, AirShipping stratejisine geç
// Yeni maliyeti hesapla ve yazdır
return 0;
}
Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.
Nesne Yönelimli Programlama bölümündeki tüm dersler
1OOP Temelleri
Harici DosyalarC++ Build ve DerlemeBaşlık Dosyaları ve Kaynak DosyalarıAd Alanları ve KapsamC++'ta OOP'ye GirişSınıflar ve Nesneler'this' İşaretçisiMetotlar (Üye Fonksiyonlar)Öznitelikler (Veri Üyeleri)Ctor ve Dtor TemelleriÖzet - Basit Hesap Makinesi4Sınıf Özellikleri
Örnek ve Statik ÜyelerGetter ve Setter MetotlarıConst Üye FonksiyonlarMutable Anahtar KelimesiStatik Metotlar ve DeğişkenlerFriend Fonksiyonlar ve SınıflarÖzet - Banka Hesabı Yöneticisi7Kalıtım
Temel KalıtımKalıtım Erişim SeviyeleriCtor ve Dtor Çağrılma SırasıMetot Geçersiz KılmaSanal Fonksiyonlar ve VTableÇoklu KalıtımSanal KalıtımÖzet - Çalışan Hiyerarşisi10STL Genel Bakış
STL Genel Bakış ve FelsefesiSTL KonteynerleriİteratörlerSTL AlgoritmalarıFunctor'lar ve Lambda İfadeleriÖzet - Kelime Frekansı13Tasarım Kalıpları 1. Bölüm
Tasarım Kalıplarına GirişSingleton KalıbıFactory ve Abstract FactoryBuilder KalıbıObserver KalıbıStrategy Kalıbı2Bellek Yönetimi
Stack ve Heap Bellekİşaretçiler ve ReferanslarDinamik Bellek (new/delete)C++'ta Akıllı İşaretçilerC++'ta RAIIÖzet - Dinamik Dizi Yöneticisi5Kapsülleme
C++'da Erişim BelirleyicilerDerinlemesine Erişim BelirleyicilerBilgi GizlemeStruct vs Classİç İçe ve Dahili SınıflarÖzet - Öğrenci Kayıt Sistemi8Çok Biçimlilik
Derleme ve Çalışma Zamanı Çok BiçimliliğiFonksiyon Aşırı YüklemeSanal Fonksiyonlara Yeniden BakışSaf Sanal FonksiyonlarSoyut SınıflarC++'ta Arayüz TasarımıDynamic Casting ve RTTIÖzet - Şekil Hesaplayıcı3Yapıcılar ve Yıkıcılar
Varsayılan YapıcıParametreli YapıcıKopya YapıcıTaşıma YapıcısıYapıcı İlklendirme ListeleriTemsilci YapıcılarYıkıcılara Derinlemesine BakışÜç / Beş / Sıfır KuralıÖzet - String Sınıfı6Operatör Aşırı Yükleme
Operatör Aşırı Yüklemeye GirişAritmetik Operatör Aşırı YüklemeKarşılaştırma Operatörü Aşırı YüklemeStream OperatörleriAtama Operatörü Aşırı Yükleme[] ve () Operatör Aşırı YüklemeTip Dönüşüm OperatörleriÖzet - Matris Sınıfı9Şablonlar
Fonksiyon ŞablonlarıSınıf ŞablonlarıŞablon ÖzelleştirmeVariadic ŞablonlarSFINAE ve Type Traits TemelleriÖzet - Generic Konteyner