Menu
Coddy logo textTech

Dekoratör Kalıbı

Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 98 / 104.

Decorator (Dekoratör) deseni, nesneleri dekoratör adı verilen özel nesnelerle sarmalayarak onlara dinamik olarak yeni davranışlar eklemenize olanak tanır. Derleme zamanında davranış ekleyen kalıtımın aksine, dekoratörler orijinal sınıfı değiştirmeden çalışma zamanında işlevselliği genişletmenize izin verir.

Desen, hem orijinal nesnenin hem de dekoratörlerin aynı arayüzü (interface) uygulamasıyla çalışır. Her dekoratör, bir bileşene (component) referans tutar ve sarmalanmış nesneye devretmeden önce veya sonra kendi davranışını ekler:

#include <iostream>
#include <memory>

// Bileşen arayüzü
class Coffee {
public:
    virtual std::string getDescription() const = 0;
    virtual double getCost() const = 0;
    virtual ~Coffee() = default;
};

// Somut bileşen
class SimpleCoffee : public Coffee {
public:
    std::string getDescription() const override { return "Coffee"; }
    double getCost() const override { return 2.0; }
};

// Temel dekoratör
class CoffeeDecorator : public Coffee {
protected:
    std::unique_ptr<Coffee> coffee;
public:
    CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};

// Somut dekoratörler
class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
    std::string getDescription() const override {
        return coffee->getDescription() + ", Milk";
    }
    double getCost() const override { return coffee->getCost() + 0.5; }
};

class SugarDecorator : public CoffeeDecorator {
public:
    SugarDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
    std::string getDescription() const override {
        return coffee->getDescription() + ", Sugar";
    }
    double getCost() const override { return coffee->getCost() + 0.2; }
};

Dekoratörler üst üste eklenebilir - her biri bir öncekini sarmalayarak işlevselliği katman katman oluşturur:

int main() {
    std::unique_ptr<Coffee> order = std::make_unique<SimpleCoffee>();
    order = std::make_unique<MilkDecorator>(std::move(order));
    order = std::make_unique<SugarDecorator>(std::move(order));
    
    std::cout << order->getDescription() << ": $" << order->getCost();
    // Çıktı: Coffee, Milk, Sugar: $2.7
}

Nesnelere, diğer nesneleri etkilemeden dinamik olarak sorumluluklar eklemeniz gerektiğinde veya alt sınıflara ayırma (subclassing) yoluyla işlevselliği genişletmenin sınıf sayısında aşırı artışa neden olacağı durumlarda Decorator kullanın.

challenge icon

Görev

Kolay

Decorator desenini kullanarak bir Pizza Sipariş Sistemi oluşturalım. Müşterilerin temel bir pizza ile başlayıp çeşitli malzemeler ekleyebileceği esnek bir sistem oluşturacaksınız; her malzeme bir önceki siparişi sarmalar ve kendi maliyetini ve açıklamasını ekler. Bu, dekoratörler için mükemmel bir kullanım durumudur: malzemeler, alt sınıfların patlamasına neden olmadan herhangi bir kombinasyonda üst üste eklenebilir.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • Pizza.h: Bileşen arayüzünü ve temel pizza sınıfını tanımlayın.

    Saf sanal metotları getDescription() (bir string döndürür) ve getCost() (bir double döndürür) olan soyut bir Pizza sınıfı ve bir sanal yıkıcı (destructor) oluşturun.

    Ardından, temel pizzayı temsil eden somut bir PlainPizza sınıfı oluşturun. Açıklama olarak "Pizza" döndürmeli ve 8.0 temel maliyetine sahip olmalıdır.

  • ToppingDecorator.h: Dekoratör temel sınıfını ve somut malzeme dekoratörlerini oluşturun.

    Pizza sınıfından miras alan ve sarmalanmış bileşene bir std::unique_ptr<Pizza> tutan bir ToppingDecorator sınıfı oluşturun. Bu, tüm malzeme dekoratörleri için temel görevi görür.

    Üç somut malzeme dekoratörü uygulayın:

    • CheeseTopping — açıklamaya ", Cheese" ve maliyete 1.5 ekler
    • PepperoniTopping — açıklamaya ", Pepperoni" ve maliyete 2.0 ekler
    • MushroomTopping — açıklamaya ", Mushrooms" ve maliyete 1.0 ekler

    Her dekoratör, sarmalanmış pizzaya delege etmeli ve ardından kendi katkısını eklemelidir.

  • main.cpp: Kullanıcı girişine göre özelleştirilmiş bir pizza siparişi oluşturun.

    Üç giriş okuyun (her biri yes veya no olacaktır):

    1. Peynir eklensin mi?
    2. Sucuk (pepperoni) eklensin mi?
    3. Mantar eklensin mi?

    Bir PlainPizza ile başlayın, ardından girişlere göre (sırasıyla: peynir, sucuk, mantar) uygun malzeme dekoratörleriyle sarmalayın. Son olarak, sipariş ayrıntılarını şu formatta yazdırın:

    Order: [description]
    Total: $[cost]

Örneğin, yes, yes, no girişleri ile:

Order: Pizza, Cheese, Pepperoni
Total: $11.5

yes, no, yes girişleri ile:

Order: Pizza, Cheese, Mushrooms
Total: $10.5

no, no, no girişleri ile:

Order: Pizza
Total: $8

yes, yes, yes girişleri ile:

Order: Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.5

Her dekoratörün bir öncekini nasıl sarmaladığına, hem açıklamayı hem de maliyeti katman katman nasıl oluşturduğuna dikkat edin. Mevcut pizza veya malzeme kodunu değiştirmeden yeni dekoratör sınıfları oluşturarak kolayca yeni malzemeler ekleyebilirsiniz; Decorator deseninin güzelliği budur.

Kopya kağıdı

Decorator deseni, nesneleri dekoratör adı verilen özel nesnelerin içine sararak onlara dinamik olarak yeni davranışlar eklemenize olanak tanır. Davranışı derleme zamanında ekleyen kalıtımın aksine, dekoratörler orijinal sınıfı değiştirmeden çalışma zamanında işlevselliği genişletmenize izin verir.

Desen, hem orijinal nesnenin hem de dekoratörlerin aynı arayüzü uygulamasıyla çalışır. Her dekoratör bir bileşene (component) referans tutar ve sarılmış nesneye devretmeden önce veya sonra kendi davranışını ekler.

Yapı

Bileşen arayüzü (Component interface): Hem somut bileşenler hem de dekoratörler için ortak arayüzü tanımlar.

class Coffee {
public:
    virtual std::string getDescription() const = 0;
    virtual double getCost() const = 0;
    virtual ~Coffee() = default;
};

Somut bileşen (Concrete component): Dekore edilebilen temel nesne.

class SimpleCoffee : public Coffee {
public:
    std::string getDescription() const override { return "Coffee"; }
    double getCost() const override { return 2.0; }
};

Temel dekoratör (Base decorator): Bir bileşene referans tutar ve aynı arayüzü uygular.

class CoffeeDecorator : public Coffee {
protected:
    std::unique_ptr<Coffee> coffee;
public:
    CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};

Somut dekoratörler (Concrete decorators): Sarılmış nesneye devrederek ve kendi işlevselliklerini ekleyerek belirli davranışlar eklerler.

class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
    std::string getDescription() const override {
        return coffee->getDescription() + ", Milk";
    }
    double getCost() const override { return coffee->getCost() + 0.5; }
};

Dekoratörleri Üst Üste Ekleme

Dekoratörler üst üste eklenebilir; her biri bir öncekini sararak işlevselliği katman katman oluşturur:

std::unique_ptr<Coffee> order = std::make_unique<SimpleCoffee>();
order = std::make_unique<MilkDecorator>(std::move(order));
order = std::make_unique<SugarDecorator>(std::move(order));

std::cout << order->getDescription() << ": $" << order->getCost();
// Çıktı: Coffee, Milk, Sugar: $2.7

Ne Zaman Kullanılır?

Diğer nesneleri etkilemeden nesnelere dinamik olarak sorumluluklar eklemeniz gerektiğinde veya alt sınıflar aracılığıyla işlevselliği genişletmek sınıf sayısında aşırı artışa neden olacağında Decorator desenini kullanın.

Kendin dene

#include <iostream>
#include <string>
#include <memory>
#include "Pizza.h"
#include "ToppingDecorator.h"

using namespace std;

int main() {
    // Üç girdi oku (yes veya no)
    string addCheese, addPepperoni, addMushrooms;
    cin >> addCheese;
    cin >> addPepperoni;
    cin >> addMushrooms;
    
    // TODO: std::unique_ptr kullanarak bir PlainPizza ile başla
    
    // TODO: Eğer addCheese "yes" ise, pizzayı CheeseTopping ile sarmala
    
    // TODO: Eğer addPepperoni "yes" ise, pizzayı PepperoniTopping ile sarmala
    
    // TODO: Eğer addMushrooms "yes" ise, pizzayı MushroomTopping ile sarmala
    
    // TODO: Siparişi şu formatta yazdır:
    // Order: [description]
    // Total: $[cost]
    
    return 0;
}
quiz iconKendini test et

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