Menu
CoddyTech

Pola Decorator

Termasuk bagian Pemrograman Berorientasi Objek dalam perjalanan Python di Coddy. Pelajaran 51 dari 64.

Pola Decorator menambahkan fungsionalitas baru ke object secara dinamis tanpa mengubah strukturnya. Pola ini membungkus object untuk memperluas perilakunya.

Berikut contoh kopi sederhana:

class Coffee:
    def cost(self):
        return 5
    
    def description(self):
        return "Simple coffee"

Buat decorator yang menambahkan fitur pada kopi:

class MilkDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 2
    
    def description(self):
        return self.coffee.description() + " + Milk"

class SugarDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 1
    
    def description(self):
        return self.coffee.description() + " + Sugar"

Setiap decorator membungkus object lain dan menambahkan fungsionalitasnya sendiri.

Gunakan decorator untuk membuat kopi sesuai keinginanmu:

# Mulai dengan kopi biasa
my_coffee = Coffee()
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

# Tambahkan susu
my_coffee = MilkDecorator(my_coffee)
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

# Tambahkan gula
my_coffee = SugarDecorator(my_coffee)
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

Buat contoh lain dengan pemformatan teks:

class Text:
    def __init__(self, content):
        self.content = content
    
    def render(self):
        return self.content

class BoldDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<b>{self.text.render()}</b>"

class ItalicDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<i>{self.text.render()}</i>"

# Buat teks berformat langkah demi langkah
message = Text("Hello World")
message = BoldDecorator(message)
message = ItalicDecorator(message)

print(message.render())

Output:

Simple coffee: $5
Simple coffee + Milk: $7
Simple coffee + Milk + Sugar: $8
<i><b>Hello World</b></i>

Poin Utama: Pola Decorator membungkus object untuk menambahkan perilaku baru tanpa mengubah object asli. Setiap decorator menyimpan referensi ke object yang dibungkus dan menambahkan fungsionalitasnya sendiri. Dengan begitu, kamu dapat menggabungkan beberapa decorator untuk mendapatkan kombinasi fitur yang fleksibel tanpa membuat banyak subclass.

challenge icon

Tantangan

Sedang

Dalam tantangan ini, kamu akan menerapkan pola desain Decorator untuk membuat sistem pemesanan kopi yang fleksibel untuk kedai kopi. Pola Decorator memungkinkan perilaku ditambahkan secara dinamis ke masing-masing object, tanpa memengaruhi perilaku object lain dari class yang sama.

Kamu sedang membangun sistem untuk kedai kopi yang perlu:

  • Menawarkan berbagai jenis minuman (Espresso, Dark Roast, House Blend, Decaf)
  • Memungkinkan pelanggan menambahkan pelengkap (Milk, Mocha, Soy, Whipped Cream) ke minuman mereka
  • Menghitung biaya minuman dengan pelengkap yang ditambahkan
  1. Lengkapi class abstrak Beverage dan implementasi konkretnya di beverage.py
  2. Terapkan class abstrak CondimentDecorator dan decorator konkret di condiment_decorator.py
  3. Terapkan skenario pengujian di driver.py untuk memverifikasi implementasimu

Coba sendiri

# Impor semua class yang diperlukan
from beverage import Espresso, HouseBlend, DarkRoast, Decaf
from condiment_decorator import Milk, Mocha, Soy, Whip

# Penangan kasus uji menyeluruh
test_case = input()

if test_case == "basic_beverage_test":
    # Uji fungsi dasar minuman
    espresso = Espresso()
    print(f"Description: {espresso.get_description()}")
    print(f"Cost: ${espresso.cost():.2f}")

elif test_case == "all_beverages_test":
    # Uji semua jenis minuman
    beverages = [Espresso(), HouseBlend(), DarkRoast(), Decaf()]
    for beverage in beverages:
        print(f"{beverage.get_description()}: ${beverage.cost():.2f}")

elif test_case == "single_condiment_test":
    # Uji penambahan satu pelengkap pada minuman
    espresso = Espresso()
    espresso_with_milk = Milk(espresso)
    print(f"Description: {espresso_with_milk.get_description()}")
    print(f"Cost: ${espresso_with_milk.cost():.2f}")

elif test_case == "multiple_condiments_test":
    # Uji penambahan beberapa pelengkap pada minuman
    house_blend = HouseBlend()
    decorated_coffee = Mocha(Soy(Whip(house_blend)))
    print(f"Description: {decorated_coffee.get_description()}")
    print(f"Cost: ${decorated_coffee.cost():.2f}")

elif test_case == "all_condiments_test":
    # Uji semua pelengkap pada satu minuman
    dark_roast = DarkRoast()
    fully_loaded = Milk(Mocha(Soy(Whip(dark_roast))))
    print(f"Description: {fully_loaded.get_description()}")
    print(f"Cost: ${fully_loaded.cost():.2f}")

elif test_case == "cost_calculation_test":
    # Uji perhitungan biaya untuk berbagai kombinasi
    espresso = Espresso()
    
    # Penambahan satu pelengkap
    with_milk = Milk(espresso)
    with_mocha = Mocha(espresso)
    
    print(f"Espresso: ${espresso.cost():.2f}")
    print(f"Espresso + Milk: ${with_milk.cost():.2f}")
    print(f"Espresso + Mocha: ${with_mocha.cost():.2f}")

elif test_case == "decoration_order_test":
    # Uji bahwa urutan penambahan pelengkap tidak memengaruhi hasil akhir
    decaf = Decaf()
    
    order1 = Milk(Mocha(decaf))
    order2 = Mocha(Milk(decaf))
    
    print(f"Order 1 - {order1.get_description()}: ${order1.cost():.2f}")
    print(f"Order 2 - {order2.get_description()}: ${order2.cost():.2f}")
    print(f"Same cost: {order1.cost() == order2.cost()}")

elif test_case == "complex_order_test":
    # Uji pesanan minuman yang kompleks
    orders = [
        Mocha(Whip(Espresso())),
        Soy(Milk(HouseBlend())),
        Whip(Mocha(Soy(DarkRoast()))),
        Milk(Decaf())
    ]
    
    for i, order in enumerate(orders, 1):
        print(f"Order {i}: {order.get_description()}")
        print(f"Cost: ${order.cost():.2f}")
        print("---")

elif test_case == "double_condiment_test":
    # Uji penambahan pelengkap yang sama beberapa kali
    espresso = Espresso()
    double_mocha = Mocha(Mocha(espresso))
    print(f"Description: {double_mocha.get_description()}")
    print(f"Cost: ${double_mocha.cost():.2f}")

elif test_case == "beverage_comparison_test":
    # Bandingkan harga berbagai minuman dengan bahan tambahan yang sama
    base_beverages = [Espresso(), HouseBlend(), DarkRoast(), Decaf()]
    
    print("All beverages with Milk + Mocha:")
    for beverage in base_beverages:
        decorated = Milk(Mocha(beverage))
        print(f"{decorated.get_description()}: ${decorated.cost():.2f}")

elif test_case == "condiment_cost_test":
    # Uji harga setiap bahan tambahan
    espresso = Espresso()
    base_cost = espresso.cost()
    
    condiments = [
        ("Milk", Milk(espresso)),
        ("Mocha", Mocha(espresso)),
        ("Soy", Soy(espresso)),
        ("Whip", Whip(espresso))
    ]
    
    for name, decorated in condiments:
        additional_cost = decorated.cost() - base_cost
        print(f"{name} adds: ${additional_cost:.2f}")

elif test_case == "cheapest_most_expensive_test":
    # Cari minuman dasar termurah dan termahal
    beverages = [
        ("Espresso", Espresso()),
        ("House Blend", HouseBlend()),
        ("Dark Roast", DarkRoast()),
        ("Decaf", Decaf())
    ]
    
    beverages.sort(key=lambda x: x[1].cost())
    print(f"Cheapest: {beverages[0][0]} - ${beverages[0][1].cost():.2f}")
    print(f"Most Expensive: {beverages[-1][0]} - ${beverages[-1][1].cost():.2f}")

elif test_case == "nested_decoration_test":
    # Uji dekorasi yang bersarang dalam
    beverage = HouseBlend()
    # Tambahkan 5 lapisan dekorasi
    decorated = Whip(Soy(Mocha(Milk(Whip(beverage)))))
    print(f"Description: {decorated.get_description()}")
    print(f"Cost: ${decorated.cost():.2f}")
    
    # Hitung jumlah bahan tambahan
    description = decorated.get_description()
    condiment_count = description.count(" + ")
    print(f"Number of condiments: {condiment_count}")

elif test_case == "cost_breakdown_test":
    # Rincian harga secara terperinci
    dark_roast = DarkRoast()
    print(f"Base Dark Roast: ${dark_roast.cost():.2f}")
    
    with_soy = Soy(dark_roast)
    print(f"+ Soy: ${with_soy.cost():.2f}")
    
    with_mocha = Mocha(with_soy)
    print(f"+ Mocha: ${with_mocha.cost():.2f}")
    
    with_whip = Whip(with_mocha)
    print(f"+ Whip: ${with_whip.cost():.2f}")
    
    final_cost = with_whip.cost()
    print(f"Final total: ${final_cost:.2f}")

elif test_case == "description_format_test":
    # Uji konsistensi format deskripsi
    espresso = Espresso()
    test_combinations = [
        Milk(espresso),
        Mocha(Milk(espresso)),
        Whip(Mocha(Milk(espresso))),
        Soy(Whip(Mocha(Milk(espresso))))
    ]
    
    for combo in test_combinations:
        description = combo.get_description()
        print(f"Description: '{description}'")
        # Periksa apakah deskripsi diawali nama minuman dasar
        starts_correctly = description.startswith("Espresso")
        print(f"Starts with base name: {starts_correctly}")
        print("---")
quiz iconUji dirimu

Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.

Semua pelajaran di Pemrograman Berorientasi Objek

Latihan sendiri: Compiler Python online