Creational
Factory
-
- Object Creation without Exposing Concrete Classes: Use the Factory pattern when you want to create objects without exposing the concrete classes to the client code. Factories provide a centralized way to create objects, allowing for better encapsulation and abstraction.
- Dynamic Object Creation: Factories are useful when the exact class of the object to be created may vary at runtime based on certain conditions or configurations. Factories can select the appropriate subclass or implementation to create based on these conditions.
- Polymorphic Object Creation: Factories enable polymorphic object creation, allowing clients to create objects without knowing the specific subclass or implementation being instantiated. This promotes loose coupling and simplifies client code.
class Burger:
def __init__(self, ingredients):
self.ingredients = ingredients
def print(self):
print(self.ingredients)
class BurgerFactory:
def createCheeseBurger(self):
ingredients = ["bun", "cheese", "beef-patty"]
return Burger(ingredients)
def createDeluxeCheeseBurger(self):
ingredients = ["bun", "tomatoe", "lettuce", "cheese", "beef-patty"]
return Burger(ingredients)
def createVeganBurger(self):
ingredients = ["bun", "special-sauce", "veggie-patty"]
return Burger(ingredients)
burgerFactory = BurgerFactory()
burgerFactory.createCheeseBurger().print()
burgerFactory.createDeluxeCheeseBurger().print()
burgerFactory.createVeganBurger().print()
Output
['bun', 'cheese', 'beef-patty']
['bun', 'tomatoe', 'lettuce', 'cheese', 'beef-patty']
['bun', 'special-sauce', 'veggie-patty']
Builder
class Burger:
def __init__(self):
self.buns = None
self.patty = None
self.cheese = None
def setBuns(self, bunStyle):
self.buns = bunStyle
def setPatty(self, pattyStyle):
self.patty = pattyStyle
def setCheese(self, cheeseStyle):
self.cheese = cheeseStyle
class BurgerBuilder:
def __init__(self):
self.burger = Burger()
def addBuns(self, bunStyle):
self.burger.setBuns(bunStyle)
return self
def addPatty(self, pattyStyle):
self.burger.setPatty(pattyStyle)
return self
def addCheese(self, cheeseStyle):
self.burger.setCheese(cheeseStyle)
return self
def build(self):
return self.burger
burger = BurgerBuilder() \
.addBuns("sesame") \
.addPatty("fish-patty") \
.addCheese("swiss cheese") \
.build()
The Builder pattern and the Factory pattern are both creational design patterns used to create objects, but they serve different purposes and are used in different scenarios. Here's when you might choose one over the other:
In summary, use the Builder pattern when you need to create complex objects with multiple parameters in a flexible and expressive manner, and use the Factory pattern when you need to encapsulate object creation, provide abstraction, and support dynamic or polymorphic object creation. Consider the complexity of the object being created and the level of control and flexibility needed in the construction process when choosing between the two patterns.
Singleton
https://www.youtube.com/watch?v=Rm4JP7JfsKY&t=634s
Why it is bad:
If you inherit from it, you can get multiple instances, which shouldn't be allowed.
Testing code is hard with singleton because you cannot create multiple fresh instances for testing
Does not work well with multi threaded applications because raise condition of
Use modules over singleton
class ApplicationState:
instance = None
def __init__(self):
self.isLoggedIn = False
@staticmethod
def getAppState():
if not ApplicationState.instance:
ApplicationState.instance = ApplicationState()
return ApplicationState.instance
appState1 = ApplicationState.getAppState()
print(appState1.isLoggedIn)
appState2 = ApplicationState.getAppState()
appState1.isLoggedIn = True
print(appState1.isLoggedIn)
print(appState2.isLoggedIn)
Output
False
True
True
Object Pool is better pattern (not in Gang of Four), but manages a fixed number of instances, like managing database connections of graphics objects with a lot of data drawn over and over again