Demystifying Design Patterns: Proxy Design Pattern

  1. Demystifying Design Patterns: Singleton Design Pattern
  2. Demystifying Design Patterns: Factory Method Design Pattern
  3. Demystifying Design Patterns: Abstract Factory Design Pattern
  4. Demystifying Design Patterns: Builder Design Pattern
  5. Demystifying Design Patterns: Prototype Design Pattern
  6. Demystifying Design Patterns: Adapter Design Pattern
  7. Demystifying Design Patterns: Bridge Design Pattern
  8. Demystifying Design Patterns: Composite Design Pattern
  9. Demystifying Design Patterns: Decorator Design Pattern
  10. Demystifying Design Patterns: Proxy Design Pattern
  11. Demystifying Design Patterns: Observer Design Pattern
  12. Demystifying Design Patterns: Strategy Design Pattern
  13. Demystifying Design Patterns: Command Design Pattern
  14. Demystifying Design Patterns: State Design Pattern
  15. Demystifying Design Patterns: Chain of Responsibility Design Pattern
  16. Demystifying Design Patterns: Visitor Design Pattern
  17. Demystifying Design Patterns: Template Method Design Pattern

Introduction

Welcome to the tenth article in our “Demystifying Design Patterns” series. In this installment, we’ll explore the Proxy Design Pattern, a structural pattern that allows you to control access to objects by serving as intermediaries or placeholders. Proxies are versatile and can be used for various purposes, including remote access, virtualization, access control, and more. In this comprehensive guide, we’ll delve into the essence of the Proxy Pattern, its use cases, and provide code examples in Java, C#, and Python to illustrate its practical applications.

Proxying Objects with the Proxy Pattern

The Proxy Pattern is all about creating a surrogate or placeholder for another object to control access to it. The core idea is to provide a representative object (the proxy) that acts as an intermediary between the client code and the real object. This intermediary can add functionalities such as lazy loading, access control, or remote communication without altering the real object’s code.

Remote Proxies and Virtual Proxies

Two common types of proxies in the Proxy Pattern are remote proxies and virtual proxies:

– Remote Proxies: These proxies are responsible for representing objects that are located in a remote address space, such as on a different server or machine. Remote proxies handle the communication and data marshaling between the client and the remote object. They are essential for distributed systems and remote procedure call (RPC) frameworks.

– Virtual Proxies: Virtual proxies are placeholders for expensive-to-create objects. Instead of initializing the real object immediately, a virtual proxy postpones its creation until it’s actually needed. This is particularly useful for optimizing performance and resource utilization. Virtual proxies are often used for scenarios like lazy loading of images or data.

Use Cases for Proxy Pattern

The Proxy Pattern finds applications in various scenarios:

1. Lazy Loading: When dealing with resource-intensive objects, a proxy can delay their creation until the client requests them. For example, in an image viewer application, the images could be loaded lazily as they are viewed, reducing memory usage and improving application startup times.

2. Remote Services: In distributed systems, remote proxies act as representatives of remote objects. They handle the complexity of network communication, making it transparent to the client. This is commonly used in web services and microservices architectures.

3. Access Control: Proxies can enforce access control policies by intercepting requests for the real object and checking permissions or credentials. For instance, a proxy can restrict access to certain methods or data based on user roles, enhancing security.

4. Caching: Virtual proxies can cache the results of expensive operations, allowing clients to reuse the cached data without invoking the real object repeatedly. This is beneficial for optimizing performance in data retrieval scenarios.

5. Monitoring and Logging: Proxies can be used to monitor and log the interactions between clients and real objects. This is valuable for debugging and auditing purposes, providing insights into system behavior.

Security and Access Control with Proxies

One of the significant advantages of the Proxy Pattern is its ability to enforce security and access control. Proxies can intercept and validate requests from clients before forwarding them to the real object. This allows you to implement fine-grained access control mechanisms. For example:

– In a banking application, a proxy can restrict access to account information based on user roles and permissions.
– In a content management system, a proxy can ensure that only authorized users are allowed to edit or delete content.

Real-Life Examples

Let’s explore real-life examples where the Proxy Pattern can be applied:

Virtual Proxy for Image Loading

Consider a photo album application where users can browse through a large collection of high-resolution images. Instead of loading all the images into memory when the application starts, a virtual proxy can be used. The virtual proxy loads images from disk or a remote server only when they are viewed, reducing memory usage and startup time.

Remote Proxy for Web Services

In a microservices architecture, different services communicate with each other through APIs. A remote proxy can encapsulate the details of making HTTP requests and handling responses. This allows services to interact seamlessly, even when they are deployed on different servers.

Access Control Proxy for Secure Data Access

In a healthcare information system, patient records contain sensitive data. An access control proxy can enforce strict access rules, ensuring that only authorized healthcare providers can access patient records. Unauthorized access attempts are intercepted and denied by the proxy.

Caching Proxy for Database Queries

In a database-driven application, frequently accessed data can be cached to improve query performance. A caching proxy intercepts database queries and checks if the results are already cached. If cached, it returns the results directly; otherwise, it forwards the query to the database and stores the results in the cache for future use.

Code Examples

Let’s explore how the Proxy Pattern can be implemented in Java, C#, and Python with practical code examples:

example-proxy-design-pattern

Java Example

// Subject interface
interface Image {
    void display();
}

// Real Object
class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading image: " + filename);


    }

    public void display() {
        System.out.println("Displaying image: " + filename);
    }
}

// Proxy
class ProxyImage implements Image {
    private RealImage realImage;
    private String filename;

    public ProxyImage(String filename) {
        this.filename = filename;
    }

    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);
        }
        realImage.display();
    }
}

public class ProxyPatternDemo {
    public static void main(String[] args) {
        Image image1 = new ProxyImage("image1.jpg");
        Image image2 = new ProxyImage("image2.jpg");

        // Images will be loaded and displayed only when needed
        image1.display();
        image2.display();
    }
}

C# Example

using System;

// Subject interface
interface IImage {
    void Display();
}

// Real Object
class RealImage : IImage {
    private string filename;

    public RealImage(string filename) {
        this.filename = filename;
        LoadFromDisk();
    }

    private void LoadFromDisk() {
        Console.WriteLine("Loading image: " + filename);
    }

    public void Display() {
        Console.WriteLine("Displaying image: " + filename);
    }
}

// Proxy
class ProxyImage : IImage {
    private RealImage realImage;
    private string filename;

    public ProxyImage(string filename) {
        this.filename = filename;
    }

    public void Display() {
        if (realImage == null) {
            realImage = new RealImage(filename);
        }
        realImage.Display();
    }
}

class ProxyPatternDemo {
    static void Main(string[] args) {
        IImage image1 = new ProxyImage("image1.jpg");
        IImage image2 = new ProxyImage("image2.jpg");

        // Images will be loaded and displayed only when needed
        image1.Display();
        image2.Display();
    }
}

Python Example

from abc import ABC, abstractmethod

# Subject interface
class Image(ABC):
    @abstractmethod
    def display(self):
        pass

# Real Object
class RealImage(Image):
    def __init__(self, filename):
        self.filename = filename
        self.load_from_disk()

    def load_from_disk(self):
        print(f"Loading image: {self.filename}")

    def display(self):
        print(f"Displaying image: {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()

if __name__ == "__main__":
    image1 = ProxyImage("image1.jpg")
    image2 = ProxyImage("image2.jpg")

    # Images will be loaded and displayed only when needed
    image1.display()
    image2.display()

Conclusion

The Proxy Design Pattern is a valuable tool for controlling access to objects and introducing additional functionalities in a flexible and non-intrusive way. Proxies, whether remote, virtual, or access control, play a crucial role in enhancing performance, security, and modularity in software design.

In our next article, we’ll dive into the Observer Design Pattern. Stay tuned for more insights into the world of design patterns!



Leave a Reply

Your email address will not be published. Required fields are marked *

*