Introduction
The Adapter pattern is a structural design pattern that allows objects with incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces by converting the interface of a class into another interface that a client expects.
Key Concepts
- Client: The class that interacts with the adapter.
- Adapter: The class that converts the interface of the adaptee into an interface the client expects.
- Adaptee: The class that needs to be adapted.
When to Use the Adapter Pattern
- When you want to use an existing class, but its interface does not match the one you need.
- When you want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don't necessarily have compatible interfaces.
- When you need to use several existing subclasses, but it's impractical to adapt their interface by subclassing every one.
Types of Adapter Patterns
- Class Adapter: Uses multiple inheritance to adapt one interface to another.
- Object Adapter: Uses composition to adapt one interface to another.
Example Scenario
Imagine you have a RoundHole
class and a SquarePeg
class. The RoundHole
class expects objects of type RoundPeg
, but you have a SquarePeg
that you need to fit into the RoundHole
.
Class Diagram
+----------------+ +----------------+ +----------------+ | RoundHole | | Adapter | | SquarePeg | +----------------+ +----------------+ +----------------+ | +fits(peg) |<-------| +fits(peg) |<-------| +getWidth() | +----------------+ +----------------+ +----------------+
Implementation
Step-by-Step Implementation
- Define the Target Interface: The interface that the client expects.
- Create the Adaptee Class: The existing class that needs to be adapted.
- Create the Adapter Class: The class that implements the target interface and translates the requests to the adaptee.
Code Example
Step 1: Define the Target Interface
class RoundPeg: def __init__(self, radius): self.radius = radius def get_radius(self): return self.radius
Step 2: Create the Adaptee Class
class SquarePeg: def __init__(self, width): self.width = width def get_width(self): return self.width
Step 3: Create the Adapter Class
class SquarePegAdapter(RoundPeg): def __init__(self, square_peg): self.square_peg = square_peg def get_radius(self): # Calculate a radius that fits the square peg return self.square_peg.get_width() * (2 ** 0.5) / 2
Step 4: Client Code
def main(): round_hole = RoundPeg(5) small_square_peg = SquarePeg(5) large_square_peg = SquarePeg(10) small_square_peg_adapter = SquarePegAdapter(small_square_peg) large_square_peg_adapter = SquarePegAdapter(large_square_peg) print(f"Small square peg fits in round hole: {round_hole.get_radius() >= small_square_peg_adapter.get_radius()}") print(f"Large square peg fits in round hole: {round_hole.get_radius() >= large_square_peg_adapter.get_radius()}") if __name__ == "__main__": main()
Explanation
- RoundPeg: The target interface that the client expects.
- SquarePeg: The adaptee class that needs to be adapted.
- SquarePegAdapter: The adapter class that translates the interface of
SquarePeg
toRoundPeg
.
Practical Exercises
Exercise 1: Implement a Class Adapter
Task: Implement a class adapter for the SquarePeg
class using multiple inheritance.
Solution:
class SquarePegAdapter(RoundPeg, SquarePeg): def __init__(self, width): SquarePeg.__init__(self, width) def get_radius(self): return self.get_width() * (2 ** 0.5) / 2 def main(): round_hole = RoundPeg(5) small_square_peg_adapter = SquarePegAdapter(5) large_square_peg_adapter = SquarePegAdapter(10) print(f"Small square peg fits in round hole: {round_hole.get_radius() >= small_square_peg_adapter.get_radius()}") print(f"Large square peg fits in round hole: {round_hole.get_radius() >= large_square_peg_adapter.get_radius()}") if __name__ == "__main__": main()
Exercise 2: Adapter for Different Libraries
Task: Create an adapter that allows a logging library with a log_message
method to be used with a client expecting a write_log
method.
Solution:
class OldLogger: def log_message(self, message): print(f"Logging message: {message}") class LoggerAdapter: def __init__(self, old_logger): self.old_logger = old_logger def write_log(self, message): self.old_logger.log_message(message) def main(): old_logger = OldLogger() logger_adapter = LoggerAdapter(old_logger) logger_adapter.write_log("This is a log message.") if __name__ == "__main__": main()
Common Mistakes and Tips
- Mistake: Forgetting to implement all methods of the target interface in the adapter.
- Tip: Ensure that the adapter class implements all methods required by the target interface.
- Mistake: Overcomplicating the adapter logic.
- Tip: Keep the adapter logic simple and focused on translating the interface.
Conclusion
The Adapter pattern is a powerful tool for making incompatible interfaces work together. By understanding and implementing this pattern, you can create flexible and reusable code that can integrate with various existing systems and libraries. This pattern is especially useful when dealing with legacy code or third-party libraries that cannot be modified.
In the next section, we will explore the Bridge pattern, another structural pattern that helps decouple an abstraction from its implementation.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What are Design Patterns?
- History and Origin of Design Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
Module 3: Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Module 5: Application of Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring Using Design Patterns
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Design Patterns in Agile Development