Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
368: The Factory Pattern
I remember a project a few years back where I was building a notification system for a growing startup. At first, it was simple: we only sent emails. A single class, a few methods, and we were gold. Then the product manager decided we needed SMS. Then Slack. Then PagerDuty for the engineering team. Before I knew it, my main service class had a massive, 200-line if/elif/else block just to decide which provider class to instantiate. Every time we added a new service, I had to touch the core business logic, and I inevitably broke the email flow while trying to get Slack working. It was a fragile mess.
That's when I realized I was fighting the code because I was forcing the "client" (the part of the app sending the notification) to know exactly how to create every single provider. The Factory Pattern is the solution to this specific headache. It allows you to decouple the creation of an object from the code that actually uses that object.
Moving Instantiation Out of the Business Logic
In Python, we don't have the rigid interfaces of Java, but the principle remains the same. Instead of your main logic worrying about which class to call, you delegate that responsibility to a "Factory." The factory is essentially a specialized object (or even just a function) whose only job is to look at the input and return the correct object.
Take a look at how we can clean up that payment or notification mess. Instead of a giant conditional block scattered throughout your app, you encapsulate that logic in one place:
class EmailProvider:
def send(self, message):
print(f"Sending Email: {message}")
class SMSProvider:
def send(self, message):
print(f"Sending SMS: {message}")
class SlackProvider:
def send(self, message):
print(f"Posting to Slack: {message}")
class NotificationFactory:
@staticmethod
def get_provider(provider_type):
providers = {
"email": EmailProvider,
"sms": SMSProvider,
"slack": SlackProvider
}
provider_class = providers.get(provider_type.lower())
if provider_class:
return provider_class()
raise ValueError(f"Unknown provider type: {provider_type}")
# Now, the main logic doesn't care which class it's using
def notify_user(user_pref, message):
provider = NotificationFactory.get_provider(user_pref)
provider.send(message)
notify_user("slack", "Your build failed!")
The Registry Pattern for Open-Closed Design
The example above is a great start, but if you look closely, you still have to edit the NotificationFactory every time you add a new provider. If you're working on a massive plugin-based system, that's still a bottleneck. To truly follow the "Open-Closed Principle"—where code is open for extension but closed for modification—you can implement a registry.
I often prefer this approach for larger projects. By allowing providers to "register" themselves with the factory, you can add new functionality without ever touching the factory's source code. It's a subtle shift, but it makes your system incredibly modular.
class ProviderRegistry:
_providers = {}
@classmethod
def register(cls, name, provider_class):
cls._providers[name] = provider_class
@classmethod
def create(cls, name):
provider_class = cls._providers.get(name)
if not provider_class:
raise ValueError(f"No provider registered for {name}")
return provider_class()
# New providers can be defined in entirely different files
class DiscordProvider:
def send(self, message):
print(f"Sending Discord message: {message}")
# Registering the new provider without changing the Registry class
ProviderRegistry.register("discord", DiscordProvider)
# Usage
client = ProviderRegistry.create("discord")
client.send("Hello from the registry!")
Knowing When to Over-Engineer
Here is a piece of advice from the trenches: don't use the Factory Pattern for everything. I've seen developers turn every single class instantiation into a factory, and it creates a nightmare of abstraction where you can't find where an object is actually being created. If you only have two classes and you know for a fact you'll never add a third, a simple if/else is perfectly fine. Use a factory when the types of objects you need to create are dynamic, depend on external configuration, or are growing in number over time.
📋 Practical Task
Exercise: Building a Multi-Format Document Exporter
You are building a reporting tool that needs to export data in various formats (JSON, XML, and CSV). Currently, the code uses a series of if statements that are becoming hard to manage.
Your task is to implement a DocumentExporterFactory that handles the creation of these exporters. Follow these requirements:
- Create three classes:
JSONExporter,XMLExporter, andCSVExporter. Each should have a methodexport(self, data)that prints"Exporting [data] to [Format]". - Implement a
DocumentExporterFactoryclass with a methodget_exporter(format_type). - The factory should use a dictionary to map format strings (e.g., "json", "xml", "csv") to their respective classes.
- Ensure the factory raises a
ValueErrorif an unsupported format is requested. - Write a small script that iterates through a list of formats
["json", "csv", "pdf"], attempts to get the exporter from the factory, and calls theexportmethod with some sample data. Handle theValueErrorgracefully when "pdf" is encountered.
There are no comments for now.