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
373: The Decorator Pattern (Structural)
Now, before we dive in, I want to clear something up. When Python developers hear "decorator," they usually think of the @decorator syntax used to wrap functions. While that's a similar concept, the Decorator Design Pattern is a structural pattern. It's about wrapping objects at runtime to add new behaviors without changing the original class or creating a massive, confusing inheritance tree.
The inheritance trap
I remember working on a notification system a few years back where I fell right into the trap I'm about to show you. I wanted a system that could send alerts via Email, SMS, and Slack. I started with a simple interface and then tried to use inheritance to handle the combinations.
class Notifier:
def send(self, message):
pass
class EmailNotifier(Notifier):
def send(self, message):
print(f"Sending Email: {message}")
class SMSNotifier(Notifier):
def send(self, message):
print(f"Sending SMS: {message}")
# Now I want both... so I make a new class?
class EmailAndSMSNotifier(Notifier):
def send(self, message):
print(f"Sending Email: {message}")
print(f"Sending SMS: {message}")
This felt fine for two options. But then the product manager asked for Slack, PagerDuty, and WhatsApp. If I kept going this way, I'd end up with EmailAndSMSAndSlackNotifier, SMSAndSlackAndWhatsAppNotifier, and so on. It's a combinatorial explosion. I was writing the same print statements over and over, and the class hierarchy was becoming a nightmare.
Wrapping objects for flexibility
The fix is to stop thinking about "is-a" (inheritance) and start thinking about "has-a" (composition). Instead of creating a class for every combination, I'll create a base decorator that implements the same interface as the Notifier but holds a reference to another Notifier object inside it.
class NotifierDecorator(Notifier):
def __init__(self, wrapped_notifier):
self._wrapped = wrapped_notifier
def send(self, message):
# By default, just delegate to the wrapped object
self._wrapped.send(message)
Now, I can create specific decorators that add their own logic before or after calling the wrapped object. This is where the magic happens: the decorator doesn't care if it's wrapping a base Notifier or another decorator.
class SMSDecorator(NotifierDecorator):
def send(self, message):
super().send(message) # Send the previous ones first
print(f"Sending SMS: {message}")
class SlackDecorator(NotifierDecorator):
def send(self, message):
super().send(message)
print(f"Sending Slack: {message}")
Stacking behaviors at runtime
Here is the part I love about this pattern. I don't have to define the combinations in my code; I can define them when the application is actually running based on user preferences.
# Basic setup
notifier = EmailNotifier()
# User wants Email AND SMS
notifier = SMSDecorator(notifier)
# User now decides they also want Slack
notifier = SlackDecorator(notifier)
# This will trigger Email -> SMS -> Slack
notifier.send("System Overload!")
Notice how notifier is still just a Notifier type. The rest of my application doesn't need to know that it's actually a SlackDecorator wrapping an SMSDecorator wrapping an EmailNotifier. It just calls .send() and the chain reacts.
A quick note on order
One thing I learned the hard way: the order of wrapping matters. If I have a LoggingDecorator that logs the start and end of a notification, wrapping it around the others will capture the entire chain. Wrapping it inside the others will only log that specific step. Always think about whether your decorator should execute its logic before or after the super().send() call.
📋 Practical Task
Building a Layered File Data Processor
You are building a system that processes strings of data before they are written to a file. Instead of creating dozens of classes for every possible combination of processing, use the Decorator Pattern to implement the following:
- A base
DataProviderclass with aget_data()method that returns a simple string (e.g., "Base data content"). - A
DataDecoratorbase class that wraps aDataProvider. - An
EncryptionDecoratorthat takes the output ofget_data()and appends " [ENCRYPTED]" to it. - A
CompressionDecoratorthat takes the output ofget_data()and appends " [COMPRESSED]" to it.
Your Goal: Create a sequence where you wrap the base provider in both encryption and compression, then call get_data() to prove that the string has been modified by both layers.
There are no comments for now.