Introduction to Creational Patterns
What Are Creational Patterns?
Section titled “What Are Creational Patterns?”Creational patterns are design patterns that deal with object creation mechanisms. They help you create objects in a way that’s flexible, maintainable, and decoupled from the rest of your code.
Think of creational patterns as smart ways to build things - instead of directly constructing objects everywhere, they provide structured approaches to object creation.
Understanding Creational Patterns
Section titled “Understanding Creational Patterns”Creational patterns focus on how objects are instantiated. They abstract the instantiation process, making your code more flexible and easier to maintain.
Key characteristics:
- Decouple object creation from object usage
- Provide flexibility in what gets created
- Centralize creation logic for better control
- Support extensibility - Easy to add new types
- Hide complexity - Client code doesn’t need to know creation details
Visual Overview
Section titled “Visual Overview”Interaction Flow Comparison
Section titled “Interaction Flow Comparison”Here’s how object creation works with and without creational patterns:
Why Do We Need Creational Patterns?
Section titled “Why Do We Need Creational Patterns?”Object creation seems simple - just use new ClassName(), right? But in real-world applications, object creation can become complex and problematic.
The Problem with Direct Object Creation
Section titled “The Problem with Direct Object Creation”When you create objects directly throughout your code, you face several challenges:
1. Tight Coupling
- Your code becomes tightly coupled to specific classes
- Changing a class name or constructor requires updating code everywhere
2. Complex Initialization
- Some objects need complex setup (multiple parameters, validation, configuration)
- This logic gets scattered across your codebase
3. Runtime Type Determination
- Sometimes you don’t know which class to instantiate until runtime
- Direct creation doesn’t handle this flexibility
4. Violation of SOLID Principles
- Hard to follow Open/Closed Principle (open for extension, closed for modification)
- Difficult to apply Dependency Inversion Principle
What’s the Use of Creational Patterns?
Section titled “What’s the Use of Creational Patterns?”Creational patterns solve common object creation problems:
1. Flexibility in Object Creation
Section titled “1. Flexibility in Object Creation”Creational patterns let you decide what type of object to create at runtime, not compile time.
Example:
# Without creational pattern - hardcodedpayment = StripePayment(api_key)
# With creational pattern - flexiblepayment = PaymentFactory.create(gateway_type, config)// Without creational pattern - hardcodedPayment payment = new StripePayment(apiKey);
// With creational pattern - flexiblePayment payment = PaymentFactory.create(gatewayType, config);// Without creational pattern - hardcodedconst payment = new StripePayment(apiKey);
// With creational pattern - flexibleconst payment = PaymentFactory.create(gatewayType, config);// Without creational pattern - hardcodedPayment* payment = new StripePayment(apiKey);
// With creational pattern - flexiblePayment* payment = PaymentFactory::create(gatewayType, config);// Without creational pattern - hardcodedvar payment = new StripePayment(apiKey);
// With creational pattern - flexiblevar payment = PaymentFactory.Create(gatewayType, config);// Without creational pattern - hardcodedpayment := NewStripePayment(apiKey)
// With creational pattern - flexiblepayment = PaymentFactory.Create(gatewayType, config)// 1. Flexibility in Object Creationstruct App;impl App { fn create_everything_directly(&self) { println!("new logger"); println!("new service"); }}2. Centralized Creation Logic
Section titled “2. Centralized Creation Logic”All object creation logic lives in one place, making it easier to:
- Maintain - Update creation logic in one spot
- Test - Mock factories easily
- Debug - Trace creation issues quickly
3. Decoupling
Section titled “3. Decoupling”Your code doesn’t depend on concrete classes - it depends on abstractions (interfaces or base classes).
Example:
# Tightly coupled - depends on concrete classdef process_payment(): stripe = StripePayment(api_key) # Hard-coded! stripe.process()
# Decoupled - depends on abstractiondef process_payment(factory): payment = factory.create() # Works with any payment type! payment.process()// Tightly coupled - depends on concrete classvoid processPayment() { var stripe = new StripePayment(apiKey); // Hard-coded! stripe.process();}
// Decoupled - depends on abstractionvoid processPayment(PaymentFactory factory) { var payment = factory.create(); // Works with any payment type! payment.process();}// Tightly coupled - depends on concrete classfunction processPayment() { const stripe = new StripePayment(apiKey); // Hard-coded! stripe.process();}
// Decoupled - depends on abstractionfunction processPayment(factory: PaymentFactory) { const payment = factory.create(); // Works with any payment type! payment.process();}// Tightly coupled - depends on concrete classvoid processPayment() { auto* stripe = new StripePayment(apiKey); // Hard-coded! stripe->process();}
// Decoupled - depends on abstractionvoid processPayment(PaymentFactory* factory) { auto* payment = factory->create(); // Works with any payment type! payment->process();}// Tightly coupled - depends on concrete classvoid ProcessPayment() { var stripe = new StripePayment(apiKey); // Hard-coded! stripe.Process();}
// Decoupled - depends on abstractionvoid ProcessPayment(IPaymentFactory factory) { var payment = factory.Create(); // Works with any payment type! payment.Process();}// Tightly coupled — depends on concrete typefunc ProcessPayment(apiKey string) { stripe := NewStripePayment(apiKey) // Hard-coded! stripe.Process()}
// Decoupled — depends on abstractionfunc ProcessPaymentWithFactory(factory IPaymentFactory) { payment := factory.Create() // Works with any payment type! payment.Process()}// 3. Decouplingtrait Service { fn run(&self);}struct DefaultService;impl Service for DefaultService { fn run(&self) { println!("service running"); }}struct ServiceFactory;impl ServiceFactory { fn create() -> Box<dyn Service> { Box::new(DefaultService) }}4. Complex Object Construction
Section titled “4. Complex Object Construction”Some objects need step-by-step construction with validation and configuration. Creational patterns handle this elegantly.
Example:
# Complex construction without pattern - messy!user = User()user.set_name("John")user.validate_email()user.set_role("admin")user.set_permissions(["read", "write"])# ... many more steps
# With Builder pattern - clean!user = UserBuilder() \ .with_name("John") \ .with_role("admin") \ .build()// Complex construction without pattern - messy!User user = new User();user.setName("John");user.validateEmail();user.setRole("admin");user.setPermissions(List.of("read", "write"));// ... many more steps
// With Builder pattern - clean!User user = new UserBuilder() .withName("John") .withRole("admin") .build();// Complex construction without pattern - messy!const user = new User();user.setName("John");user.validateEmail();user.setRole("admin");user.setPermissions(["read", "write"]);// ... many more steps
// With Builder pattern - clean!const user = new UserBuilder() .withName("John") .withRole("admin") .build();// Complex construction without pattern - messy!User user;user.setName("John");user.validateEmail();user.setRole("admin");user.setPermissions({"read", "write"});// ... many more steps
// With Builder pattern - clean!User user = UserBuilder() .withName("John") .withRole("admin") .build();// Complex construction without pattern - messy!var user = new User();user.SetName("John");user.ValidateEmail();user.SetRole("admin");user.SetPermissions(new[] { "read", "write" });// ... many more steps
// With Builder pattern - clean!var user = new UserBuilder() .WithName("John") .WithRole("admin") .Build();// Complex construction without pattern — messy!user := NewUser()user.SetName("John")user.ValidateEmail()user.SetRole("admin")user.SetPermissions([]string{"read", "write"})// ... many more steps
// With Builder pattern — clean!user := NewUserBuilder(). WithName("John"). WithRole("admin"). Build()// 4. Complex Object Constructionstruct App;impl App { fn create_everything_directly(&self) { println!("new logger"); println!("new service"); }}5. Resource Management
Section titled “5. Resource Management”Some objects should only exist once (like database connections). Creational patterns ensure proper resource management.
What Happens If We Don’t Use Creational Patterns?
Section titled “What Happens If We Don’t Use Creational Patterns?”Without creational patterns, you’ll face several problems:
1. Scattered Creation Logic
Section titled “1. Scattered Creation Logic”Object creation code spreads throughout your codebase, making it hard to:
- Find where objects are created
- Update creation logic
- Understand the system
Example:
# ❌ Creation logic scattered everywheredef checkout(): payment = StripePayment(stripe_key) # Created here # ... checkout logic
def refund(): payment = StripePayment(stripe_key) # Created again here # ... refund logic
def subscription(): payment = StripePayment(stripe_key) # Created again! # ... subscription logic// ❌ Creation logic scattered everywherevoid checkout() { var payment = new StripePayment(stripeKey); // Created here // ... checkout logic}void refund() { var payment = new StripePayment(stripeKey); // Created again here // ... refund logic}void subscription() { var payment = new StripePayment(stripeKey); // Created again! // ... subscription logic}// ❌ Creation logic scattered everywherefunction checkout() { const payment = new StripePayment(stripeKey); // Created here // ... checkout logic}function refund() { const payment = new StripePayment(stripeKey); // Created again here // ... refund logic}function subscription() { const payment = new StripePayment(stripeKey); // Created again! // ... subscription logic}// ❌ Creation logic scattered everywherevoid checkout() { auto* payment = new StripePayment(stripeKey); // Created here // ... checkout logic}void refund() { auto* payment = new StripePayment(stripeKey); // Created again here // ... refund logic}void subscription() { auto* payment = new StripePayment(stripeKey); // Created again! // ... subscription logic}// ❌ Creation logic scattered everywherevoid Checkout() { var payment = new StripePayment(stripeKey); // Created here // ... checkout logic}void Refund() { var payment = new StripePayment(stripeKey); // Created again here // ... refund logic}void Subscription() { var payment = new StripePayment(stripeKey); // Created again! // ... subscription logic}// ❌ Creation logic scattered everywherefunc Checkout() { payment := NewStripePayment(stripeKey) // Created here // ... checkout logic}func Refund() { payment := NewStripePayment(stripeKey) // Created again here // ... refund logic}func Subscription() { payment := NewStripePayment(stripeKey) // Created again! // ... subscription logic}// 1. Scattered Creation Logictrait Service { fn run(&self);}struct DefaultService;impl Service for DefaultService { fn run(&self) { println!("service running"); }}struct ServiceFactory;impl ServiceFactory { fn create() -> Box<dyn Service> { Box::new(DefaultService) }}Problems:
- If Stripe API changes, you need to update 3+ places
- Hard to switch to a different payment provider
- Can’t easily test with mock payments
2. Tight Coupling
Section titled “2. Tight Coupling”Your code becomes tightly coupled to specific classes, making it hard to:
- Switch implementations
- Add new types
- Test with mocks
Example:
# ❌ Tightly coupled to StripePaymentclass OrderService: def process_order(self): payment = StripePayment(api_key) # Hard-coded! payment.process()
# If you want to use PayPal, you need to modify this class!// ❌ Tightly coupled to StripePaymentclass OrderService { void processOrder() { var payment = new StripePayment(apiKey); // Hard-coded! payment.process(); }}// If you want to use PayPal, you need to modify this class!// ❌ Tightly coupled to StripePaymentclass OrderService { processOrder() { const payment = new StripePayment(apiKey); // Hard-coded! payment.process(); }}// If you want to use PayPal, you need to modify this class!// ❌ Tightly coupled to StripePaymentclass OrderService { void processOrder() { auto* payment = new StripePayment(apiKey); // Hard-coded! payment->process(); }};// If you want to use PayPal, you need to modify this class!// ❌ Tightly coupled to StripePaymentclass OrderService { void ProcessOrder() { var payment = new StripePayment(apiKey); // Hard-coded! payment.Process(); }}// If you want to use PayPal, you need to modify this class!// ❌ Tightly coupled to StripePaymenttype OrderService struct{}
func (*OrderService) ProcessOrder(apiKey string) { payment := NewStripePayment(apiKey) // Hard-coded! payment.Process()}// If you want to use PayPal, you need to modify this type!// 2. Tight Couplingtrait Service { fn run(&self);}struct DefaultService;impl Service for DefaultService { fn run(&self) { println!("service running"); }}struct ServiceFactory;impl ServiceFactory { fn create() -> Box<dyn Service> { Box::new(DefaultService) }}3. Violation of SOLID Principles
Section titled “3. Violation of SOLID Principles”Without creational patterns, you often violate:
- Open/Closed Principle - Need to modify code to add new types
- Dependency Inversion - Depend on concrete classes, not abstractions
- Single Responsibility - Classes handle both business logic and object creation
4. Complex Initialization Scattered
Section titled “4. Complex Initialization Scattered”When objects need complex setup, that logic gets duplicated everywhere:
Example:
# ❌ Complex initialization duplicateddef create_user_v1(): user = User() user.set_name(name) user.set_email(email) user.validate() user.set_role("user") user.initialize_permissions() return user
def create_user_v2(): user = User() user.set_name(name) # Duplicated! user.set_email(email) # Duplicated! user.validate() # Duplicated! # ... same logic repeated// ❌ Complex initialization duplicatedUser createUserV1() { User user = new User(); user.setName(name); user.setEmail(email); user.validate(); user.setRole("user"); user.initializePermissions(); return user;}User createUserV2() { User user = new User(); user.setName(name); // Duplicated! user.setEmail(email); // Duplicated! user.validate(); // Duplicated! // ... same logic repeated return user;}// ❌ Complex initialization duplicatedfunction createUserV1() { const user = new User(); user.setName(name); user.setEmail(email); user.validate(); user.setRole("user"); user.initializePermissions(); return user;}function createUserV2() { const user = new User(); user.setName(name); // Duplicated! user.setEmail(email); // Duplicated! user.validate(); // Duplicated! // ... same logic repeated return user;}// ❌ Complex initialization duplicatedUser createUserV1() { User user; user.setName(name); user.setEmail(email); user.validate(); user.setRole("user"); user.initializePermissions(); return user;}User createUserV2() { User user; user.setName(name); // Duplicated! user.setEmail(email); // Duplicated! user.validate(); // Duplicated! // ... same logic repeated return user;}// ❌ Complex initialization duplicatedUser CreateUserV1() { var user = new User(); user.SetName(name); user.SetEmail(email); user.Validate(); user.SetRole("user"); user.InitializePermissions(); return user;}User CreateUserV2() { var user = new User(); user.SetName(name); // Duplicated! user.SetEmail(email); // Duplicated! user.Validate(); // Duplicated! // ... same logic repeated return user;}// ❌ Complex initialization duplicatedfunc CreateUserV1(name, email string) *User { user := NewUser() user.SetName(name) user.SetEmail(email) user.Validate() user.SetRole("user") user.InitializePermissions() return user}
func CreateUserV2(name, email string) *User { user := NewUser() user.SetName(name) // Duplicated! user.SetEmail(email) // Duplicated! user.Validate() // Duplicated! // ... same logic repeated return user}// 4. Complex Initialization Scatteredtrait Service { fn run(&self);}struct DefaultService;impl Service for DefaultService { fn run(&self) { println!("service running"); }}struct ServiceFactory;impl ServiceFactory { fn create() -> Box<dyn Service> { Box::new(DefaultService) }}5. Hard to Test
Section titled “5. Hard to Test”Without creational patterns, testing becomes difficult:
- Can’t easily swap real objects with test doubles
- Need to set up complex dependencies everywhere
- Hard to isolate units for testing
Simple Example: The Problem We’re Solving
Section titled “Simple Example: The Problem We’re Solving”Let’s see a simple example that shows why creational patterns matter:
The Scenario: Building a Notification System
Section titled “The Scenario: Building a Notification System”You’re building a notification system that can send messages via Email, SMS, or Push notifications.
Visual Comparison
Section titled “Visual Comparison”Interaction Flow: Notification System
Section titled “Interaction Flow: Notification System”Here’s how the notification system works with and without creational patterns:
Without Creational Pattern
Section titled “Without Creational Pattern”# ❌ Without Creational Pattern - Problems everywhere!
class EmailNotification: def send(self, message): print(f"📧 Email sent: {message}")
class SMSNotification: def send(self, message): print(f"📱 SMS sent: {message}")
# Problem: Creation logic scattered everywheredef checkout(): notification = EmailNotification() # Created here notification.send("Order confirmed")
def refund(): notification = EmailNotification() # Created again here notification.send("Refund processed")
def subscribe(): notification = EmailNotification() # Created again! notification.send("Subscription active")
# Problems:# - Creation logic duplicated 3 times# - Hard to switch to SMS (need to change 3 places)# - Tight coupling to EmailNotification// ❌ Without Creational Pattern - Problems everywhere!
public class EmailNotification { public void send(String message) { System.out.println("📧 Email sent: " + message); }}
public class SMSNotification { public void send(String message) { System.out.println("📱 SMS sent: " + message); }}
// Problem: Creation logic scattered everywherepublic class OrderService { public void checkout() { EmailNotification notification = new EmailNotification(); // Created here notification.send("Order confirmed"); }
public void refund() { EmailNotification notification = new EmailNotification(); // Created again here notification.send("Refund processed"); }
public void subscribe() { EmailNotification notification = new EmailNotification(); // Created again! notification.send("Subscription active"); }}
// Problems:// - Creation logic duplicated 3 times// - Hard to switch to SMS (need to change 3 places)// - Tight coupling to EmailNotification}// ❌ Without Creational Pattern - Problems everywhere!
class EmailNotification { send(message: string): void { console.log(`📧 Email sent: ${message}`); }}
class SMSNotification { send(message: string): void { console.log(`📱 SMS sent: ${message}`); }}
// Problem: Creation logic scattered everywherefunction checkout(): void { const notification = new EmailNotification(); // Created here notification.send("Order confirmed");}
function refund(): void { const notification = new EmailNotification(); // Created again here notification.send("Refund processed");}
function subscribe(): void { const notification = new EmailNotification(); // Created again! notification.send("Subscription active");}
// Problems:// - Creation logic duplicated 3 times// - Hard to switch to SMS (need to change 3 places)// - Tight coupling to EmailNotification// ❌ Without Creational Pattern - Problems everywhere!
#include <iostream>#include <string>
class EmailNotification {public: void send(const std::string& message) { std::cout << "📧 Email sent: " << message << std::endl; }};
class SMSNotification {public: void send(const std::string& message) { std::cout << "📱 SMS sent: " << message << std::endl; }};
// Problem: Creation logic scattered everywherevoid checkout() { EmailNotification notification; // Created here notification.send("Order confirmed");}
void refund() { EmailNotification notification; // Created again here notification.send("Refund processed");}
void subscribe() { EmailNotification notification; // Created again! notification.send("Subscription active");}
// Problems:// - Creation logic duplicated 3 times// - Hard to switch to SMS (need to change 3 places)// - Tight coupling to EmailNotification// ❌ Without Creational Pattern - Problems everywhere!
using System;
public class EmailNotification{ public void Send(string message) { Console.WriteLine($"📧 Email sent: {message}"); }}
public class SMSNotification{ public void Send(string message) { Console.WriteLine($"📱 SMS sent: {message}"); }}
// Problem: Creation logic scattered everywherepublic class OrderService{ public void Checkout() { EmailNotification notification = new EmailNotification(); // Created here notification.Send("Order confirmed"); }
public void Refund() { EmailNotification notification = new EmailNotification(); // Created again here notification.Send("Refund processed"); }
public void Subscribe() { EmailNotification notification = new EmailNotification(); // Created again! notification.Send("Subscription active"); }}
// Problems:// - Creation logic duplicated 3 times// - Hard to switch to SMS (need to change 3 places)// - Tight coupling to EmailNotification// ❌ Without Creational Pattern - Problems everywhere!
package main
import "fmt"
type EmailNotification struct{}
func (EmailNotification) Send(message string) { fmt.Printf("📧 Email sent: %s\n", message)}
type SMSNotification struct{}
func (SMSNotification) Send(message string) { fmt.Printf("📱 SMS sent: %s\n", message)}
// Problem: Creation logic scattered everywheretype OrderService struct{}
func (*OrderService) Checkout() { notification := EmailNotification{} // Created here notification.Send("Order confirmed")}
func (*OrderService) Refund() { notification := EmailNotification{} // Created again here notification.Send("Refund processed")}
func (*OrderService) Subscribe() { notification := EmailNotification{} // Created again! notification.Send("Subscription active")}
// Problems:// - Creation logic duplicated 3 times// - Hard to switch to SMS (need to change 3 places)// - Tight coupling to EmailNotification// Without Creational Patternstruct App;impl App { fn create_everything_directly(&self) { println!("new logger"); println!("new service"); }}Problems:
- ❌ Creation logic scattered across multiple functions
- ❌ Hard to add new notification types (need to modify multiple places)
- ❌ Tight coupling to specific notification classes
- ❌ Can’t easily switch notification methods
- ❌ Duplicated initialization code
With Creational Pattern (Factory)
Section titled “With Creational Pattern (Factory)”# ✅ With Creational Pattern - Clean and flexible!
from abc import ABC, abstractmethod
# Step 1: Define the interfaceclass Notification(ABC): @abstractmethod def send(self, message: str) -> None: pass
# Step 2: Create concrete implementationsclass EmailNotification(Notification): def send(self, message: str) -> None: print(f"📧 Email sent: {message}")
class SMSNotification(Notification): def send(self, message: str) -> None: print(f"📱 SMS sent: {message}")
# Step 3: Create the Factoryclass NotificationFactory: @staticmethod def create(notification_type: str) -> Notification: """Factory method - centralized creation logic""" if notification_type == "email": return EmailNotification() elif notification_type == "sms": return SMSNotification() else: raise ValueError(f"Unknown type: {notification_type}")
# Step 4: Use the factory - clean and simple!def checkout(): notification = NotificationFactory.create("email") # Factory handles creation notification.send("Order confirmed")
def refund(): notification = NotificationFactory.create("email") # Same factory method notification.send("Refund processed")
# Benefits:# - Creation logic in one place# - Easy to switch to SMS (change "email" to "sms")# - Decoupled from specific classes// ✅ With Creational Pattern - Clean and flexible!
// Step 1: Define the interfacepublic interface Notification { void send(String message);}
// Step 2: Create concrete implementationspublic class EmailNotification implements Notification { @Override public void send(String message) { System.out.println("📧 Email sent: " + message); }}
public class SMSNotification implements Notification { @Override public void send(String message) { System.out.println("📱 SMS sent: " + message); }}
// Step 3: Create the Factorypublic class NotificationFactory { public static Notification create(String notificationType) { // Factory method - centralized creation logic if ("email".equals(notificationType)) { return new EmailNotification(); } else if ("sms".equals(notificationType)) { return new SMSNotification(); } else { throw new IllegalArgumentException("Unknown type: " + notificationType); } }}
// Step 4: Use the factory - clean and simple!public class OrderService { public void checkout() { Notification notification = NotificationFactory.create("email"); // Factory handles creation notification.send("Order confirmed"); }
public void refund() { Notification notification = NotificationFactory.create("email"); // Same factory method notification.send("Refund processed"); }}
// Benefits:// - Creation logic in one place// - Easy to switch to SMS (change "email" to "sms")// - Decoupled from specific classes}// ✅ With Creational Pattern - Clean and flexible!
// Step 1: Define the interfaceinterface Notification { send(message: string): void;}
// Step 2: Create concrete implementationsclass EmailNotification implements Notification { send(message: string): void { console.log(`📧 Email sent: ${message}`); }}
class SMSNotification implements Notification { send(message: string): void { console.log(`📱 SMS sent: ${message}`); }}
// Step 3: Create the Factoryclass NotificationFactory { static create(notificationType: string): Notification { /** Factory method - centralized creation logic */ if (notificationType === "email") { return new EmailNotification(); } else if (notificationType === "sms") { return new SMSNotification(); } else { throw new Error(`Unknown type: ${notificationType}`); } }}
// Step 4: Use the factory - clean and simple!function checkout(): void { const notification = NotificationFactory.create("email"); // Factory handles creation notification.send("Order confirmed");}
function refund(): void { const notification = NotificationFactory.create("email"); // Same factory method notification.send("Refund processed");}
// Benefits:// - Creation logic in one place// - Easy to switch to SMS (change "email" to "sms")// - Decoupled from specific classes// ✅ With Creational Pattern - Clean and flexible!
#include <iostream>#include <string>#include <memory>#include <stdexcept>
// Step 1: Define the interfaceclass Notification {public: virtual ~Notification() = default; virtual void send(const std::string& message) = 0;};
// Step 2: Create concrete implementationsclass EmailNotification : public Notification {public: void send(const std::string& message) override { std::cout << "📧 Email sent: " << message << std::endl; }};
class SMSNotification : public Notification {public: void send(const std::string& message) override { std::cout << "📱 SMS sent: " << message << std::endl; }};
// Step 3: Create the Factoryclass NotificationFactory {public: // Factory method - centralized creation logic static std::unique_ptr<Notification> create(const std::string& notificationType) { if (notificationType == "email") { return std::make_unique<EmailNotification>(); } else if (notificationType == "sms") { return std::make_unique<SMSNotification>(); } else { throw std::invalid_argument("Unknown type: " + notificationType); } }};
// Step 4: Use the factory - clean and simple!void checkout() { auto notification = NotificationFactory::create("email"); // Factory handles creation notification->send("Order confirmed");}
void refund() { auto notification = NotificationFactory::create("email"); // Same factory method notification->send("Refund processed");}
// Benefits:// - Creation logic in one place// - Easy to switch to SMS (change "email" to "sms")// - Decoupled from specific classes// ✅ With Creational Pattern - Clean and flexible!
using System;
// Step 1: Define the interfacepublic interface INotification{ void Send(string message);}
// Step 2: Create concrete implementationspublic class EmailNotification : INotification{ public void Send(string message) { Console.WriteLine($"📧 Email sent: {message}"); }}
public class SMSNotification : INotification{ public void Send(string message) { Console.WriteLine($"📱 SMS sent: {message}"); }}
// Step 3: Create the Factorypublic class NotificationFactory{ public static INotification Create(string notificationType) { // Factory method - centralized creation logic if (notificationType == "email") { return new EmailNotification(); } else if (notificationType == "sms") { return new SMSNotification(); } else { throw new ArgumentException($"Unknown type: {notificationType}"); } }}
// Step 4: Use the factory - clean and simple!public class OrderService{ public void Checkout() { INotification notification = NotificationFactory.Create("email"); // Factory handles creation notification.Send("Order confirmed"); }
public void Refund() { INotification notification = NotificationFactory.Create("email"); // Same factory method notification.Send("Refund processed"); }}
// Benefits:// - Creation logic in one place// - Easy to switch to SMS (change "email" to "sms")// - Decoupled from specific classes// ✅ With Creational Pattern - Clean and flexible!
package main
import ( "fmt")
// Step 1: Define the interfacetype Notification interface { Send(message string)}
// Step 2: Create concrete implementationstype EmailNotification struct{}
func (EmailNotification) Send(message string) { fmt.Printf("📧 Email sent: %s\n", message)}
type SMSNotification struct{}
func (SMSNotification) Send(message string) { fmt.Printf("📱 SMS sent: %s\n", message)}
// Step 3: Create the Factorytype NotificationFactory struct{}
func (NotificationFactory) Create(notificationType string) Notification { // Factory method — centralized creation logic switch notificationType { case "email": return EmailNotification{} case "sms": return SMSNotification{} default: panic(fmt.Sprintf("Unknown type: %s", notificationType)) }}
// Step 4: Use the factory - clean and simple!type OrderService struct{}
func (*OrderService) Checkout() { notification := NotificationFactory{}.Create("email") // Factory handles creation notification.Send("Order confirmed")}
func (*OrderService) Refund() { notification := NotificationFactory{}.Create("email") // Same factory method notification.Send("Refund processed")}
// Benefits:// - Creation logic in one place// - Easy to switch to SMS (change "email" to "sms")// - Decoupled from specific types// With Creational Pattern Factorytrait Service { fn run(&self);}struct DefaultService;impl Service for DefaultService { fn run(&self) { println!("service running"); }}struct ServiceFactory;impl ServiceFactory { fn create() -> Box<dyn Service> { Box::new(DefaultService) }}Benefits:
- ✅ Centralized creation - All creation logic in one place
- ✅ Easy to extend - Add new types without modifying existing code
- ✅ Decoupled - Code doesn’t depend on specific notification classes
- ✅ Flexible - Can switch notification methods easily
- ✅ Testable - Easy to mock the factory
Class Structure
Section titled “Class Structure”Types of Creational Patterns
Section titled “Types of Creational Patterns”Creational patterns come in different flavors, each solving specific creation problems:
Problem: Need to create objects without specifying exact classes
Solution: Use a factory method that creates objects based on input
When to use: When object creation depends on runtime conditions
Problem: Complex objects with many optional parameters
Solution: Build objects step-by-step with a fluent interface
When to use: When objects need complex construction with validation
Problem: Need exactly one instance of a class
Solution: Ensure only one instance exists and provide global access
When to use: When you need a single point of control (database connections, loggers)
Problem: Creating objects is expensive
Solution: Clone existing instances instead of creating new ones
When to use: When object creation is costly and you have similar objects
Problem: Need to create families of related objects
Solution: Provide an interface for creating families of objects
When to use: When you need to create multiple related objects together
Key Takeaways
Section titled “Key Takeaways”What We Learned
Section titled “What We Learned”- Creational patterns focus on how objects are created
- Why we need them: Direct creation leads to tight coupling, scattered logic, and hard-to-maintain code
- What they solve: Flexibility, decoupling, centralized logic, complex construction
- What happens without them: Scattered code, tight coupling, SOLID violations, hard testing
Next Steps
Section titled “Next Steps”Now that you understand creational patterns, explore specific patterns:
- Factory Pattern - Create objects without knowing exact classes
- Abstract Factory Pattern - Create families of related objects
- Builder Pattern - Build complex objects step by step
- Prototype Pattern - Clone existing objects
- Singleton Pattern - Ensure only one instance exists
Remember: Creational patterns are about making object creation flexible, maintainable, and decoupled. Use them wisely! 🏭