Builder pattern veya builder method isimlendirmeleriyle kaynaklarlarda rastlayabileceğiniz konsept creational/yaratımsal bir tasarım kalıbıdır. Uygulamalarımızı geliştirirken zaman zaman ihtiyaçlar değişir ve uygulamanın buna ayak uydurması gerekir, bu durumunda complextyi artıracağı bir gerçektir. __init__ metodunda 10 farklı parametre alan bir class düşünün bu classı bir yerlerde çağırıp instance’ını almak biraz can sıkıcı olacaktır. Builder patternin çözüm sunduğu noktalardan birisi budur, bir nesneyi inşa etme işini builder nesnesine devreder, bu sayede sınıf özellikleri gruplanarak instance’ın oluşturulabilmesi daha okunabilir ve temiz şekilde sağlanır. Bir örnek üzerinden gidecek olursak;
İsim, doğum tarihi, pozisyon değerleriyle initialize edilen bir Employee classımız olsun.
class Employee:
def __init__(self) -> None:
self.name = None
self.position = None
self.date_of_birth = None
def __str__(self) -> str:
return f'{self.name} named employee, birth at {self.date_of_birth} and working as {self.position}'
Employee sınıfını initialize ederken name, position ve date_of_birth değişkenlerinin değerlerini None olarak atadık ve bu classtan bir instance oluşturmaya çalıştığımızda __init__ metodundanda bu değişkenlerin değerini almadık, ancak classımızı print ettiğimizde çalışacak __str__ methodu içinde bu değişkenleri kullanıyoruz, bu değişkenleri bir şekilde set etmemiz gerekecek işte tamda bu noktada devreye builder design el atıyor olacak.
class EmployeeBuilder:
def __init__(self, employee=Employee()) -> None:
self.employee = employee
def build(self):
return self.employee
EmployeeBuilder sınıfı initialize edilirken bir employee objesi alıyor ve build methodu çağırıldığında instance’ı geri dönüyor. İnitialization(ilk sırada çalışacak) ve build(son sırada çalışacak) methodlarının çalışmaları arasında istediğimiz özellikleri Employee instanceımıza ekliyor olacağız.
class EmployeeInfoBuilder(EmployeeBuilder):
def called(self, name):
self.employee.name = name
return self
class EmployeePositionBuilder(EmployeeInfoBuilder):
def work_as_a(self, position):
self.employee.position = position
return self
class EmployeeBirthDateBuilder(EmployeePositionBuilder):
def born(self, date_of_birth):
self.employee.date_of_birth = date_of_birth
return self
Dikkat edeceğimiz nokta builder sınıfılarını özelleştirerek, istediğimiz özellikleri ayırdığımız bir yapı oluşturduk, yani çalışana isim eklemek istediğimde EmployeeInfoBuilder, pozisyon eklemek istediğimde ise EmployeePositionBuilder sınıfları üzerindeki metodlara gerekli parametreleri paslayarak instance’ımıza set etmiş oluyoruz.
employee_builder = EmployeeBirthDateBuilder()
me = employee_builder\
.called('Kadir')\
.work_as_a('Developer')\
.born('1990-01-01')\
.build()
print(me)
----------------------------------------------------------------------
Output:
Kadir named employee, birth at 1990-01-01 and working as Developer
PersonBirthDateBuilder üzerinden tüm bu build işleminin yürüdüğünü görüyoruz, burda hiyerarşik ve sıralı bir yapı olduğunu gözden kaçırmamakta yarar var en fazla özelleştirdiğimiz sınıf PersonBirthDateBuilder ve bu sınıf InfoBuilder ve PositionBuilder’e ait özellikleride içeriyor, bu yüzden builder instance’ını BirthDateBuilder sınıfımız üzerinden oluşturuyoruz. date_of_birth değişkenini set etmek amacımız olsaydı ve builder instance’ını PositionBuilder üzerinden oluştursaydık, date_of_birth değişkenini set eden born() metodunu kullanamayacaktık.
Örneği biraz daha geliştirelim ve bir e-ticaret uygulamasında elektronik ve yiyecek ürünleri sattığımızı düşünelim, elektronik ürünler ve yiyecek ürünleri arasında şu farklar mevcut olsun:
- Elektronik ürünlerin vergisi %18’dir.
- Elektronik ürünlerde belirlenen indirim tutarına ek 5 birim ekstra indirim yapılmaktadır.
- Elektronik ürünlerin toplam stoğu her zaman 1 ürün eksik olacak şekilde gösterilmelidir.
- Yiyecek ürünlerinin vergisi %8’dir.
- Yiyecek ürünleri belirlenen net fiyat üzerinden 5 birim indirimli şekilde satışa sunulmaktadır.
- Yiyecek ürünlerinde stok miktarının bir önemi yoktur.
bu kurallar doğrultusunda builder design ile geliştirme yaptığımızda kodumuz şöyle olacaktır.
from abc import ABC, abstractmethod
class Product(object):
def __init__(self) -> None:
self.name = None
self.net_price = None
self.total_price = None
self.discount = None
self.stock = None
class ProductBuilder(ABC):
@property
@abstractmethod
def product(self) -> Product:
pass
@abstractmethod
def set_name(self, name:str) -> None:
pass
@abstractmethod
def add_net_price(self, price:float) -> None:
pass
@abstractmethod
def add_stock(self, stock:int) -> None:
pass
@abstractmethod
def add_discount_amount(self, discount:float) -> None:
pass
@abstractmethod
def apply_discount(self) -> float:
pass
class ConcreteProductBuilder(ProductBuilder):
def __init__(self) -> None:
self._product = Product()
@property
def product(self) -> None:
return self._product
def apply_discount(self):
self.product.total_price = self.product.net_price - self.product.discount * (1+self.tax_ratio)
class FoodProductBuilder(ConcreteProductBuilder):
def set_name(self, name):
self.product.name = name
return self
def add_net_price(self, net_price):
self.product.net_price = net_price - 5
return self
def add_discount_amount(self, discount):
self.product.discount = discount
return self
def add_stock(self, stock):
self.product.stock = stock
return self
class ElectronicProductBuilder(ConcreteProductBuilder):
def set_name(self, name):
self.product.name = name
return self
def add_net_price(self, net_price):
self.product.net_price = net_price
return self
def add_discount_amount(self, discount):
self.product.discount = discount + 5
return self
def add_stock(self, stock):
self.product.stock = stock - 1
return self
class ProductDirector():
def __init__(self, product_builder:ProductBuilder) -> None:
self.product_builder = product_builder
def build(self):
if isinstance(self.product_builder, FoodProductBuilder):
self.product_builder.tax_ratio = 0.08
else:
self.product_builder.tax_ratio = 0.18
self.product_builder.apply_discount()
return self.product_builder.product
food_builder = FoodProductBuilder()
food_builder.set_name("Bread")\
.add_discount_amount(0)\
.add_net_price(10)
fpd = ProductDirector(food_builder)
print(fpd.build().__dict__)
electronic_builder = ElectronicProductBuilder()
electronic_builder.set_name("Pc")\
.add_net_price(1000)\
.add_discount_amount(50)\
.add_stock(500)
epd = ProductDirector(electronic_builder)
print(epd.build().__dict__)
-----------------------------------------------------------------------
Output:
{'name': 'Bread', 'net_price': 5, 'total_price': 5.0, 'discount': 0, 'stock': None}
{'name': 'Pc', 'net_price': 1000, 'total_price': 935.1, 'discount': 55, 'stock': 499}
böylelikle farklı tiplerdeki ürünleri yaratırken vergi, stok, indirim gibi hesaplamarının özelleştirilmesini ve bu ürünlerin yaratılma süreçlerinin tek bir obje üzerinden yürütülmesini sağlamış oluyoruz.
Sources:
Be First to Comment