Object Creation with Creational Design Patterns in Python
Dhiyanesh Sidhaiyan
Fostering Excellence in Development Teams: Streamlining the Art of Software Construction | ACV Auctions
Object creation is a fundamental aspect of object-oriented programming. But how you create objects can significantly impact the flexibility, maintainability, and complexity of your code. Here's where Creational Design Patterns come in. These patterns provide standardized approaches for object creation, addressing various scenarios. Let's explore some common Creational Patterns in Python with detailed explanations and examples:
1. Singleton Pattern:
Purpose: Ensures only one instance of a class exists throughout the application. This is useful for managing global resources
Implementation:
class Logger:
_instance = None # Private attribute to store the singleton instance
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, filename):
self.filename = filename
# Initialize logging logic here
def log(self, message):
# Write message to the log file
pass
# Usage
logger = Logger("app.log") # Only one instance will be created
logger.log("This is a log message.")
Explanation:
Benefits:
Drawbacks:
2. Factory Method Pattern:
Purpose: Creates objects of a specific type without exposing details of the concrete subclass creation process. This provides flexibility when you need to create different types of objects based on conditions.
Implementation:
Python
class Enemy:
def __init__(self, name, health, attack):
self.name = name
self.health = health
self.attack = attack
def attack(self):
print(f"{self.name} attacks for {self.attack} damage!")
class EnemyFactory:
@staticmethod
def create_enemy(enemy_type):
if enemy_type == "orc":
return Orc()
elif enemy_type == "goblin":
return Goblin()
else:
raise ValueError("Invalid enemy type")
class Orc(Enemy):
def __init__(self):
super().__init__("Orc", 100, 15)
class Goblin(Enemy):
def __init__(self):
super().__init__("Goblin", 50, 8)
# Usage
enemy_type = input("Choose enemy (orc, goblin): ")
enemy = EnemyFactory.create_enemy(enemy_type)
enemy.attack() # Prints appropriate attack message based on enemy type
Explanation:
Benefits:
3. Builder Pattern:
Purpose: Creates complex objects step-by-step, allowing for flexible object construction. This is useful for building objects with many optional or interdependent parts.
领英推荐
Implementation:
Python
class Pizza:
def __init__(self, dough, sauce, toppings=[]):
self.dough = dough
self.sauce = sauce
self.toppings = toppings
def __str__(self):
return f"Pizza: Dough - {self.dough}, Sauce - {self.sauce}, Toppings - {', '.join(self.toppings)}"
class PizzaBuilder:
def __init__(self):
self.dough = None
self.sauce = None
self.toppings = []
def set_dough(self, dough):
self.dough = dough
return self
def set_sauce(self, sauce):
self.sauce = sauce
return self
def add_topping(self, topping):
self.toppings.append(topping)
return self
def build(self):
return Pizza(self.dough, self.sauce, self.toppings)
# Usage
pizza = PizzaBuilder()\
.set_dough("Thin Crust")\
.set_sauce("Tomato")\
.add_topping("Mushrooms")\
.add_topping("Peppers")\
.build()
print(pizza) # Output: Pizza: Dough - Thin Crust, Sauce - Tomato, Toppings - Mushrooms, Peppers
Explanation:
Benefits:
4. Prototype Pattern:
Purpose: Copies existing objects to create new ones efficiently. This is useful for objects that are expensive to create (e.g., database connections, network connections) or act as templates for similar objects.
Implementation:
Python
import copy
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def __clone__(self):
return copy.deepcopy(self)
class UserManager:
def __init__(self):
self.default_user = User("Default User", "[email protected]")
def get_user(self):
return self.default_user.__clone__() # Clone the default user
# Usage
user_manager = UserManager()
user1 = user_manager.get_user()
user1.name = "John Doe"
user2 = user_manager.get_user()
user2.email = "[email protected]"
print(user1.name, user1.email) # Output: John Doe [email protected]
print(user2.name, user2.email) # Output: Default User [email protected]
# Modifications to user1 and user2 don't affect each other because they are deep copies.
Explanation:
Benefits:
5. Abstract Factory Pattern:
Purpose: Creates families of related objects without specifying their concrete classes. This is useful when you need to create sets of objects that belong together (e.g., different UI themes, database connections).
Implementation:
Python
class Button:
def __init__(self, label):
self.label = label
def click(self):
print(f"Button '{self.label}' clicked!")
class Window:
def __init__(self, title):
self.title = title
def show(self):
print(f"Window '{self.title}' displayed.")
class UIFactory:
def create_button(self):
raise NotImplementedError
def create_window(self):
raise NotImplementedError
class WinFactory(UIFactory):
def create_button(self):
return Button("Windows Button")
The best Creational Design Pattern depends on your specific needs. Here's a quick guideline: