Adapter Pattern
Part of the Object Oriented Programming section of Coddy's Python journey — lesson 50 of 64.
The Adapter Pattern allows objects with incompatible interfaces to work together. It acts as a bridge by wrapping an existing class with a new interface that clients expect.
Here are two systems with incompatible interfaces:
class OldPrinter:
def old_print(self, text):
return f"OLD: {text}"
class NewPrinter:
def print(self, text):
return f"NEW: {text}"The old printer uses old_print() while the new one uses print().
Create an adapter to make the old printer work with the new interface:
class PrinterAdapter:
def __init__(self, old_printer):
self.old_printer = old_printer
def print(self, text):
# Adapt old interface to new interface
return self.old_printer.old_print(text)The adapter wraps the old printer and provides the expected interface.
Use both printers with the same client code:
def print_document(printer, text):
return printer.print(text) # Expects print() method
# Use new printer directly
new_printer = NewPrinter()
print(print_document(new_printer, "Hello"))
# Use old printer through adapter
old_printer = OldPrinter()
adapter = PrinterAdapter(old_printer)
print(print_document(adapter, "Hello"))Create another example with media players:
class Mp3Player:
def play_mp3(self, filename):
return f"Playing MP3: {filename}"
class Mp4Player:
def play_mp4(self, filename):
return f"Playing MP4: {filename}"
class MediaAdapter:
def __init__(self, player, audio_type):
self.player = player
self.audio_type = audio_type
def play(self, filename):
if self.audio_type == "mp3":
return self.player.play_mp3(filename)
elif self.audio_type == "mp4":
return self.player.play_mp4(filename)class AudioPlayer:
def play(self, audio_type, filename):
if audio_type == "mp3":
return Mp3Player().play_mp3(filename)
else:
adapter = MediaAdapter(Mp4Player(), audio_type)
return adapter.play(filename)
player = AudioPlayer()
print(player.play("mp3", "song.mp3"))
print(player.play("mp4", "video.mp4"))Output:
NEW: Hello
OLD: Hello
Playing MP3: song.mp3
Playing MP4: video.mp4Key Point: The Adapter Pattern makes incompatible interfaces work together by wrapping an existing class with a new interface. The adapter translates calls from the expected interface to the actual interface of the wrapped object. This is useful for integrating legacy code or third-party libraries without modifying existing code.
Challenge
MediumIn this challenge, you will implement the Adapter design pattern to integrate legacy systems with a modern application architecture. The Adapter pattern allows objects with incompatible interfaces to work together by creating a wrapper (adapter) that translates one interface to another.
Your company has a legacy data analysis and visualization system that needs to be integrated with a new modern analytics platform. The legacy components have interfaces that are incompatible with the new system. Instead of rewriting the legacy code (which would be risky and expensive), you've been tasked with creating adapters to make these components work with the new system.
You need to:
- Understand the modern interfaces defined in
interfaces.py(this file cannot be modified) - Examine the legacy components in
legacy_system.py(this file cannot be modified) - Implement adapter classes in
adapters.pythat make the legacy components compatible with the modern interfaces - Create a modern system in
modern_system.pythat uses these interfaces
Cheat sheet
The Adapter Pattern allows objects with incompatible interfaces to work together by wrapping an existing class with a new interface that clients expect.
Basic adapter structure:
class PrinterAdapter:
def __init__(self, old_printer):
self.old_printer = old_printer
def print(self, text):
# Adapt old interface to new interface
return self.old_printer.old_print(text)Example with incompatible printer interfaces:
class OldPrinter:
def old_print(self, text):
return f"OLD: {text}"
class NewPrinter:
def print(self, text):
return f"NEW: {text}"
# Client code expects print() method
def print_document(printer, text):
return printer.print(text)
# Use old printer through adapter
old_printer = OldPrinter()
adapter = PrinterAdapter(old_printer)
print(print_document(adapter, "Hello")) # OLD: HelloMedia player adapter example:
class MediaAdapter:
def __init__(self, player, audio_type):
self.player = player
self.audio_type = audio_type
def play(self, filename):
if self.audio_type == "mp3":
return self.player.play_mp3(filename)
elif self.audio_type == "mp4":
return self.player.play_mp4(filename)The adapter translates calls from the expected interface to the actual interface of the wrapped object, enabling integration of legacy code without modification.
Try it yourself
# Import all necessary classes
from adapters import LegacyDataAnalyzerAdapter, LegacyChartGeneratorAdapter
from modern_system import AnalyticsSystem
# Comprehensive test case handler
test_case = input()
if test_case == "basic_adapter_test":
# Test basic adapter functionality
processor = LegacyDataAnalyzerAdapter()
data = [1, 2, 3, 4, 5]
processor.process_data(data)
results = processor.get_results()
print(f"Results: {results}")
elif test_case == "visualizer_adapter_test":
# Test visualizer adapter
visualizer = LegacyChartGeneratorAdapter("bar")
data = [10, 20, 30, 40, 50]
success = visualizer.visualize(data)
if success:
export_success = visualizer.export_visualization("test_chart.png")
print(f"Visualization exported: {export_success}")
elif test_case == "analytics_system_test":
# Test complete analytics system
processor = LegacyDataAnalyzerAdapter()
visualizer = LegacyChartGeneratorAdapter("line")
system = AnalyticsSystem(processor, visualizer)
data = [15, 25, 35, 45, 55]
result = system.analyze_and_visualize(data, "analytics_output.png")
print(f"System result: {result}")
elif test_case == "validation_error_test":
# Test validation errors
processor = LegacyDataAnalyzerAdapter()
try:
processor.process_data("invalid_data")
print("Validation failed - should have raised ValueError")
except ValueError as e:
print(f"Validation error caught: {e}")
elif test_case == "empty_data_test":
# Test with empty data
processor = LegacyDataAnalyzerAdapter()
try:
processor.process_data([])
results = processor.get_results()
print(f"Empty data results: {results}")
except Exception as e:
print(f"Empty data error: {e}")
elif test_case == "chart_types_test":
# Test different chart types
chart_types = ["bar", "line", "pie"]
data = [5, 15, 25, 35]
for chart_type in chart_types:
visualizer = LegacyChartGeneratorAdapter(chart_type)
success = visualizer.visualize(data)
print(f"{chart_type} chart created: {success}")
elif test_case == "file_extension_test":
# Test file extension handling
visualizer = LegacyChartGeneratorAdapter()
data = [1, 2, 3, 4]
visualizer.visualize(data)
# Test various filename formats
filenames = ["test", "test.jpg", "test.pdf", "test.png"]
for filename in filenames:
success = visualizer.export_visualization(filename)
print(f"Export '{filename}': {success}")
elif test_case == "summary_test":
# Test analysis summary
processor = LegacyDataAnalyzerAdapter()
visualizer = LegacyChartGeneratorAdapter()
system = AnalyticsSystem(processor, visualizer)
data = [10, 20, 30, 40, 50, 60]
system.analyze_and_visualize(data)
summary = system.get_analysis_summary()
print("Analysis Summary:")
print(summary)
elif test_case == "mixed_data_test":
# Test with mixed numeric data
processor = LegacyDataAnalyzerAdapter()
data = [1.5, 2, 3.7, 4, 5.2]
processor.process_data(data)
results = processor.get_results()
print(f"Mixed data results: {results}")
elif test_case == "invalid_numeric_test":
# Test with invalid numeric data
processor = LegacyDataAnalyzerAdapter()
try:
processor.process_data([1, 2, "three", 4, 5])
print("Validation failed - should have raised ValueError")
except ValueError as e:
print(f"Invalid numeric data error: {e}")
elif test_case == "no_results_summary_test":
# Test summary with no results
processor = LegacyDataAnalyzerAdapter()
visualizer = LegacyChartGeneratorAdapter()
system = AnalyticsSystem(processor, visualizer)
summary = system.get_analysis_summary()
print(f"No results summary: {summary}")
elif test_case == "large_dataset_test":
# Test with larger dataset
processor = LegacyDataAnalyzerAdapter()
data = list(range(1, 101)) # 1 to 100
processor.process_data(data)
results = processor.get_results()
print(f"Large dataset - Mean: {results.get('mean', 'N/A'):.2f}")
print(f"Large dataset - Min: {results.get('min', 'N/A')}")
print(f"Large dataset - Max: {results.get('max', 'N/A')}")
elif test_case == "complete_workflow_test":
# Test complete workflow with multiple operations
processor = LegacyDataAnalyzerAdapter()
bar_visualizer = LegacyChartGeneratorAdapter("bar")
line_visualizer = LegacyChartGeneratorAdapter("line")
data = [12, 18, 25, 32, 28, 35, 42]
# Create analytics systems with different visualizers
bar_system = AnalyticsSystem(processor, bar_visualizer)
line_system = AnalyticsSystem(LegacyDataAnalyzerAdapter(), line_visualizer)
# Run analyses
bar_result = bar_system.analyze_and_visualize(data, "bar_chart.png")
line_result = line_system.analyze_and_visualize(data, "line_chart.png")
print("Complete workflow test completed")
print(f"Bar chart result: {bar_result['visualization_file']}")
print(f"Line chart result: {line_result['visualization_file']}")
# Get summaries
print("Bar system summary:")
print(bar_system.get_analysis_summary())
print("Line system summary:")
print(line_system.get_analysis_summary())This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe self ParameterMethodsAttributesConstructor Method (__init__)Recap - Simple Calculator4Inheritance
Basic InheritanceThe super() FunctionMethod OverridingMultiple InheritanceMethod Resolution OrderRecap - Employee Hierarchy7Special Methods
Magic Methods IntroductionOperator OverloadingContainer Magic MethodsRecap - Custom List10Design Patterns Part 1
Intro to design patternSingleton PatternFactory PatternObserver PatternStrategy Pattern2Decorators
Introduction to DecoratorsProperty DecoratorStatic Method DecoratorClass Method Decorator5Polymorphism
Method Overriding RevisitedDuck TypingAbstract Classes and MethodsInterface DesignRecap - Shape Calculator8Advanced OOP Concepts
Composition vs InheritanceMixinsStatic and Class MethodsClass DecoratorsContext Managers11Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite Pattern3Class Properties
Instance vs Class VariablesProperty DecoratorsPrivate AttributesRecap - Bank Account Manager6Encapsulation
Public, Protected, Private MemAccess ModifiersInformation HidingProperty Decorators AdvancedRecap - Student Records System12Project: Library Management
Project OverviewBook and User Classes