Structural design patterns are concerned with how classes and objects are composed to form larger structures. These patterns help ensure that if one part of a system changes, the entire system doesn't need to change as well. They focus on simplifying the design by identifying a simple way to realize relationships between entities.
Key Concepts
- Composition Over Inheritance: Structural patterns often emphasize composition over inheritance. This means that instead of creating a complex class hierarchy, you can achieve the same functionality by composing objects.
- Object Relationships: These patterns help manage relationships between objects to ensure that they can work together effectively.
- Flexibility and Reusability: By using structural patterns, you can create more flexible and reusable code.
Common Structural Patterns
Here are some of the most commonly used structural design patterns:
| Pattern | Description |
|---|---|
| Adapter | Allows incompatible interfaces to work together. |
| Bridge | Separates an object’s interface from its implementation. |
| Composite | Composes objects into tree structures to represent part-whole hierarchies. |
| Decorator | Adds additional responsibilities to an object dynamically. |
| Facade | Provides a simplified interface to a complex subsystem. |
| Flyweight | Reduces the cost of creating and manipulating a large number of similar objects. |
| Proxy | Provides a surrogate or placeholder for another object. |
Example: Adapter Pattern
The Adapter pattern allows objects with incompatible interfaces to collaborate. It acts as a bridge between two incompatible interfaces.
Example Scenario
Imagine you have a MediaPlayer interface that plays audio files and a AdvancedMediaPlayer interface that plays both audio and video files. You want to use AdvancedMediaPlayer in place of MediaPlayer.
Code Example
# MediaPlayer interface
class MediaPlayer:
def play(self, audio_type, file_name):
pass
# AdvancedMediaPlayer interface
class AdvancedMediaPlayer:
def play_vlc(self, file_name):
pass
def play_mp4(self, file_name):
pass
# Concrete implementation of AdvancedMediaPlayer
class VlcPlayer(AdvancedMediaPlayer):
def play_vlc(self, file_name):
print(f"Playing vlc file. Name: {file_name}")
def play_mp4(self, file_name):
pass
class Mp4Player(AdvancedMediaPlayer):
def play_vlc(self, file_name):
pass
def play_mp4(self, file_name):
print(f"Playing mp4 file. Name: {file_name}")
# Adapter class implementing MediaPlayer
class MediaAdapter(MediaPlayer):
def __init__(self, audio_type):
if audio_type == "vlc":
self.advanced_music_player = VlcPlayer()
elif audio_type == "mp4":
self.advanced_music_player = Mp4Player()
def play(self, audio_type, file_name):
if audio_type == "vlc":
self.advanced_music_player.play_vlc(file_name)
elif audio_type == "mp4":
self.advanced_music_player.play_mp4(file_name)
# Concrete implementation of MediaPlayer
class AudioPlayer(MediaPlayer):
def __init__(self):
self.media_adapter = None
def play(self, audio_type, file_name):
if audio_type == "mp3":
print(f"Playing mp3 file. Name: {file_name}")
elif audio_type in ["vlc", "mp4"]:
self.media_adapter = MediaAdapter(audio_type)
self.media_adapter.play(audio_type, file_name)
else:
print(f"Invalid media. {audio_type} format not supported")
# Client code
if __name__ == "__main__":
audio_player = AudioPlayer()
audio_player.play("mp3", "beyond_the_horizon.mp3")
audio_player.play("mp4", "alone.mp4")
audio_player.play("vlc", "far_far_away.vlc")
audio_player.play("avi", "mind_me.avi")Explanation
- Interfaces:
MediaPlayerandAdvancedMediaPlayerdefine the interfaces for playing media files. - Concrete Implementations:
VlcPlayerandMp4Playerimplement theAdvancedMediaPlayerinterface. - Adapter:
MediaAdapterimplements theMediaPlayerinterface and uses an instance ofAdvancedMediaPlayerto play the appropriate file format. - Client:
AudioPlayerusesMediaAdapterto play different types of media files.
Practical Exercise
Task
Create a Shape interface with a draw method. Implement two concrete classes, Rectangle and Circle, that implement the Shape interface. Then, create a ShapeMaker class that uses the Facade pattern to draw these shapes.
Solution
# Shape interface
class Shape:
def draw(self):
pass
# Concrete implementations
class Rectangle(Shape):
def draw(self):
print("Drawing a Rectangle")
class Circle(Shape):
def draw(self):
print("Drawing a Circle")
# Facade class
class ShapeMaker:
def __init__(self):
self.rectangle = Rectangle()
self.circle = Circle()
def draw_rectangle(self):
self.rectangle.draw()
def draw_circle(self):
self.circle.draw()
# Client code
if __name__ == "__main__":
shape_maker = ShapeMaker()
shape_maker.draw_rectangle()
shape_maker.draw_circle()Explanation
- Shape Interface: Defines the
drawmethod. - Concrete Implementations:
RectangleandCircleimplement theShapeinterface. - Facade:
ShapeMakerprovides a simplified interface to draw shapes. - Client: Uses
ShapeMakerto draw shapes without knowing the underlying implementation.
Summary
In this section, we introduced structural design patterns, which focus on how classes and objects are composed to form larger structures. We discussed the importance of composition over inheritance and provided an example of the Adapter pattern. We also included a practical exercise to reinforce the concepts learned. In the next sections, we will delve deeper into individual structural patterns and their applications.
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
