logologo
  • AI Tools

    DB Query GeneratorMock InterviewResume Builder
  • XpertoAI
  • MVP Ready
  • Resources

    CertificationsTopicsExpertsCoursesArticlesQuestionsVideosJobs
logologo

Elevate Your Coding with our comprehensive articles and niche courses.

Useful Links

  • Contact Us
  • Privacy Policy
  • Terms & Conditions
  • Refund & Cancellation
  • About Us

Resources

  • Xperto-AI
  • Certifications
  • Python
  • GenAI
  • Machine Learning

Interviews

  • DSA
  • System Design
  • Design Patterns
  • Frontend System Design
  • ReactJS

Procodebase © 2024. All rights reserved.

Level Up Your Skills with Xperto-AI

A multi-AI agent platform that helps you level up your development skills and ace your interview preparation to secure your dream job.

Launch Xperto-AI

Understanding Structural Design Patterns

author
Generated by
ProCodebase AI

15/01/2025

AI Generateddesign-patterns

When we talk about software design patterns, we often think about tools and methodologies that can help programmers tackle common problems in a more efficient way. Structural design patterns, in particular, focus on how objects and classes are composed to form larger, more complex structures. They aim to ensure that if one part of a system changes, the entire system doesn’t need to change with it. This blog aims to break down key structural patterns, making them easy to understand and apply in your coding projects.

1. Adapter Pattern

Imagine you have an old electrical appliance that uses a two-prong plug, but your new house has only three-prong outlets. You need an adapter! In software, the Adapter Pattern serves a similar purpose—allowing incompatible interfaces to work together.

Example

Suppose you have a legacy system that outputs data in XML format, but you need your application to work with JSON. Here’s how it can look:

# Legacy system class XMLData: def get_data(self): return '<data><item>Value</item></data>' # Desired interface class JSONData: def get_json(self): raise NotImplementedError() # Adapter class XMLToJSONAdapter(JSONData): def __init__(self, xml_data): self.xml_data = xml_data def get_json(self): # Simple conversion logic return '{"data": {"item": "Value"}}' # Usage xml_data_instance = XMLData() adapter = XMLToJSONAdapter(xml_data_instance) json_output = adapter.get_json() print(json_output) # Output: {"data": {"item": "Value"}}

In this example, the XMLToJSONAdapter allows our application to consume XML data in a JSON-friendly format seamlessly.

2. Bridge Pattern

The Bridge Pattern is designed to separate the abstraction from the implementation, allowing both to evolve independently. This pattern is helpful when you want to avoid a proliferation of classes by using an interface as a bridge between the two.

Example

Consider the scenario of different styles of notifications (Email, SMS) and different types of messages (Text, Image):

# Abstraction class Notification: def __init__(self, msg_sender): self.msg_sender = msg_sender def send(self, message): self.msg_sender.send_message(message) # Implementor class MessageSender: def send_message(self, message): raise NotImplementedError() # Concrete Implementors class EmailSender(MessageSender): def send_message(self, message): print(f'Sending Email: {message}') class SMSSender(MessageSender): def send_message(self, message): print(f'Sending SMS: {message}') # Usage email_notification = Notification(EmailSender()) sms_notification = Notification(SMSSender()) email_notification.send('Hello via Email!') sms_notification.send('Hello via SMS!')

In this example, the Notification class acts as a bridge, letting you change either the type of sender or the message type independently.

3. Composite Pattern

The Composite Pattern is incredibly useful when dealing with tree structures. It lets you treat individual objects and compositions of objects uniformly. This is particularly beneficial for file systems or UI components.

Example

Suppose we want to manage a group of shapes (Circles and Rectangles):

# Component class Shape: def draw(self): raise NotImplementedError() # Leaf class Circle(Shape): def draw(self): print('Drawing Circle') class Rectangle(Shape): def draw(self): print('Drawing Rectangle') # Composite class Drawing(Shape): def __init__(self): self.shapes = [] def add(self, shape): self.shapes.append(shape) def draw(self): for shape in self.shapes: shape.draw() # Usage drawing = Drawing() drawing.add(Circle()) drawing.add(Rectangle()) drawing.draw() # Output: # Drawing Circle # Drawing Rectangle

In this case, the Drawing class can include both individual Shape instances and other Drawing instances, making it easier to manage complex structures.

4. Decorator Pattern

The Decorator Pattern allows behavior to be added to individual objects dynamically without affecting the behavior of other objects from the same class. This provides a flexible alternative to subclassing.

Example

Let's enhance a simple text message with different formats:

# Component class Message: def get_content(self): return 'Hello World' # Decorator class MessageDecorator: def __init__(self, message): self.message = message def get_content(self): return self.message.get_content() # Concrete Decorators class BoldDecorator(MessageDecorator): def get_content(self): return f'<b>{self.message.get_content()}</b>' class ItalicDecorator(MessageDecorator): def get_content(self): return f'<i>{self.message.get_content()}</i>' # Usage simple_message = Message() bold_message = BoldDecorator(simple_message) italic_message = ItalicDecorator(bold_message) print(italic_message.get_content()) # Output: <i><b>Hello World</b></i>

In this case, you can stack multiple decorators to compose your message in various ways.

5. Proxy Pattern

The Proxy Pattern provides a surrogate or placeholder for another object to control access to it. This is handy in scenarios such as lazy loading, access control, or logging demands.

Example

Let’s explore a simple image loading scenario:

# Subject class Image: def display(self): raise NotImplementedError() # Real Subject class RealImage(Image): def __init__(self, filename): self.filename = filename self.load_image() def load_image(self): print(f'Loading {self.filename}') def display(self): print(f'Displaying {self.filename}') # Proxy class ProxyImage(Image): def __init__(self, filename): self.filename = filename self.real_image = None def display(self): if self.real_image is None: self.real_image = RealImage(self.filename) self.real_image.display() # Usage image = ProxyImage('test_image.jpg') image.display() # Output: Loading test_image.jpg # Displaying test_image.jpg

Here, the ProxyImage controls access to RealImage, preventing it from loading until it’s necessary.

With these foundational structural design patterns, you can enhance your application's flexibility and maintainability. Implementing these will keep your code well-organized and responsive to changing requirements, fostering a better overall architectural design. Dive into these examples, experiment with them, and harness the power of structural design patterns in your next software project!

Popular Tags

design-patternsstructural-patternssoftware-development

Share now!

Like & Bookmark!

Related Courses

  • Mastering SOLID Principles in Python

    10/02/2025 | Design Patterns

  • Design Patterns Simplified: A Beginner's Guide

    15/01/2025 | Design Patterns

  • Architectural Design Patterns

    12/10/2024 | Design Patterns

  • Mastering SOLID Principles

    06/09/2024 | Design Patterns

  • Creational Design Patterns Deep Dive

    09/10/2024 | Design Patterns

Related Articles

  • Interface Segregation Principle

    10/02/2025 | Design Patterns

  • Overview of Creational Design Patterns

    15/01/2025 | Design Patterns

  • Understanding the Builder Pattern for Object Construction

    15/01/2025 | Design Patterns

  • Understanding the Composite Pattern for Tree-Like Structures

    15/01/2025 | Design Patterns

  • Exploring Factory Method and Its Applications

    15/01/2025 | Design Patterns

  • Understanding the Bridge Pattern

    15/01/2025 | Design Patterns

  • Harnessing the Strategy Pattern for Algorithm Flexibility

    15/01/2025 | Design Patterns

Popular Category

  • Python
  • Generative AI
  • Machine Learning
  • ReactJS
  • System Design