Proxy design pattern - Java - Explained

Proxy design pattern - Java - Explained

Intent

Provide a surrogate or placeholder for another object to control access to it.
This design pattern is also known as "surrogate".
It's a type of structural design pattern.

Motivation behind the pattern/problem it solves?

The Proxy pattern provides a flexible and powerful way to manage the interactions between clients and objects.

Below are the common problems where proxy pattern can be used.
Access control: The Proxy pattern can be used to restrict access to an object by providing a proxy with limited access to the object. For example, a proxy can be used to restrict access to a file or a database by controlling the permissions of the proxy object.

Resource management: Can be used to manage resources such as memory, network connections, and database connections. For example, a proxy can be used to manage a connection pool for a database by creating and managing connections on behalf of the client.

Performance optimization: This pattern can be used to improve the performance of an application by caching frequently used data or delaying the creation of expensive objects until they are needed. For example, a proxy can be used to cache the results of a database query to avoid repeatedly querying the database for the same data.

Remote access: The Proxy pattern can be used to provide remote access to an object by providing a proxy that communicates with the object over a network. For example, a proxy can be used to provide access to a remote database by communicating with the database over a network.

design problem scenario

Problem Statement 1 (proxy for cache ):

We have a requirement to cache the productPrice to save external systems calls everytime to fetch the price. When first time price is received from the external system then that price must get cached and for later calls, the price must be sent from the cached value.
check the complete code here github_link
solution

***
public interface Product {
    public int getPrice();
}

***
public class ConcreteProduct implements Product {
    private String name;
    private int price;

    public ConcreteProduct(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public int getPrice() {
        return this.price;
    }
}

***
public class ProductProxy implements Product {
    private ConcreteProduct product;
    private Integer priceCache = null;

    public ProductProxy(ConcreteProduct product) {
        this.product = product;
    }

    public int getPrice() {
        if (this.priceCache == null) {
            System.out.println("calling different system to get price first time");
            this.priceCache = this.product.getPrice();
        }

        return this.priceCache;
    }
}

****
public class ProxyPatternSimpleMain {
    public static void main(String[] args) {

        ProductProxy productProxy1 = new ProductProxy(new ConcreteProduct("iPhone", 1000));

        // First call gets price from the Product object and caches it
        System.out.println(productProxy1.getPrice());

        // Second call returns the cached price
        System.out.println(productProxy1.getPrice());
    }
}

*** output ***
calling different system to get price first time
1000
1000

In the above code, we have an interface Product that defines the contract for a product. We then have a concrete implementation ConcreteProduct that implements the Product interface and provides a getPrice() method that returns the price of the product.

We also have a ProductProxy class that implements the Product interface and acts as a proxy for the ConcreteProduct object. The ProductProxy class has a reference to a ConcreteProduct object and a cache to store the price of the product. When the getPrice() method is called on the ProductProxy object, it first checks if the price is already cached. If the price is not cached, it calls the getPrice() method on the ConcreteProduct object and caches the result. If the price is already cached, it returns the cached price.

In the Main class, we create a ProductProxy object, and call the getPrice() method on the ProductProxy object twice. The first call gets the price from the ConcreteProduct object and caches it in the ProductProxy object. The second call returns the cached price without accessing the ConcreteProduct object again.

Problem Statement 2 (proxy used for access-control):

We have a requirement to secure payment method calls, before calling the processPayment() on the actual payment object user must be authenticated and as s fraud check user must not try processing payment more than 5 times.

check complete code here github_link

code example

**
public class ProxyPatternMain {
    public static void main(String[] args) {

        // create a new user context
        UserContext.setCurrentUser(new User("john.cena", "password"));
        // uncomment to make user authorized
        UserContext.getCurrentUser().setAuthorized(true);

        // create a new payment processor proxy
        PaymentProcessor processor = new PaymentProcessorProxy();

        // create a new order
        Order order = new Order("123456", 100.00);

        // process the payment
        try {
            processor.processPayment(order);
            /*processor.processPayment(order);
            processor.processPayment(order);
            processor.processPayment(order);
            processor.processPayment(order);
            processor.processPayment(order);
            processor.processPayment(order);
            processor.processPayment(order);*/

            System.out.println("Payment processed successfully.");
        } catch (PaymentRequestsLimitExceededException e) {
            System.out.println("Payment request limit exceeded: " + e.getMessage());
        } catch (UnauthorizedAccessException e) {
            System.out.println("Unauthorized access: " + e.getMessage());
        }
    }
}

**
public class PaymentProcessorProxy implements PaymentProcessor {
    private PaymentProcessor realProcessor;
    public static final int MAX_PAYMENT_REQUESTS = 5;

    public PaymentProcessorProxy() {
        this.realProcessor = new RealPaymentProcessor();
    }

    public void processPayment(Order order) throws UnauthorizedAccessException, PaymentRequestsLimitExceededException {
        // check if user is authorized to process payments
        if (!UserContext.getCurrentUser().isAuthorized()) {
            throw new UnauthorizedAccessException("User is not authorized to process payments.");
        }

        // check if user has exceeded maximum number of payment processing requests
        if (UserContext.getCurrentUser().getPaymentRequestsCount() >= MAX_PAYMENT_REQUESTS) {
            throw new PaymentRequestsLimitExceededException("User has exceeded the maximum number of payment requests.");
        }

        // delegate actual payment processing to real processor
        this.realProcessor.processPayment(order);

        // update user's payment requests count
        UserContext.getCurrentUser().incrementPaymentRequestsCount();
    }
}
***
public class RealPaymentProcessor implements PaymentProcessor {
    public void processPayment(Order order) {
        // perform actual payment processing
        System.out.println("Processing payment for order: " + order.getId());
    }
}

*** output ***
Processing payment for order: 123456
Payment processed successfully.

** For complete code please check the repo .

Structure: proxy pattern

proxy pattern UML , references : wiki

Components in the proxy pattern

  1. Subject: The common interface or base class that defines the operations that the real subject and proxy must implement.

  2. RealSubject: The real object that the proxy represents and delegates requests to.

  3. Proxy: The object that acts as an intermediary between the client and the real subject, providing additional functionality or control over access to the real subject.

  4. Client: The object that uses the proxy to interact with the real subject.

In the Proxy pattern, the client interacts with the proxy object, which in turn delegates requests to the real subject-object. The proxy object can perform additional tasks before or after delegating the request to the real subject, such as caching or logging. The client is unaware of the proxy's existence and interacts with it in the same way as it would with the real subject.

Applicability

  1. Remote Method Invocation (RMI): In Java RMI, a client calls a remote method on a server object. The server object may be in a different JVM or on a different machine. The Proxy pattern is used to create a local proxy object that represents the remote server object and delegates method calls to it. The proxy object can handle communication with the remote object, such as marshalling and unmarshalling parameters and return values.

  2. Lazy Loading: In applications that deal with large datasets, it may not be efficient to load all the data into memory at once. The Proxy pattern can be used to create a proxy object that lazily loads data from a database or other source as it is needed. The proxy object can cache data that has already been loaded to improve performance.

  3. Security: The Proxy pattern can be used to add security to an object. For example, a proxy object can check if the client has the necessary permissions before delegating a method call to the real subject object.

  4. AOP in Spring: The Spring Framework uses the Proxy pattern extensively for implementing Aspect-Oriented Programming (AOP). Spring AOP allows you to add cross-cutting concerns, such as logging, security, and caching, to your application without modifying the source code of the application. Spring AOP uses dynamic proxies or byte code instrumentation to intercept method calls and apply advice.

  5. Performance Optimization: The Proxy pattern can be used to optimize the performance of an object. For example, a proxy object can cache expensive operations or optimize network communication.

  6. Logging and Monitoring: The Proxy pattern can be used to add logging and monitoring to an object. For example, a proxy object can log method calls and their parameters for debugging purposes.

Pros and Cons

Pros

  1. Flexibility: The Proxy pattern provides a flexible way to add functionality to an object without modifying its implementation.

  2. Controlled Access: The Proxy pattern allows you to control access to an object by providing a layer of indirection between the client and the object. This can be useful for implementing security, caching, and other features.

  3. Improved Performance: The Proxy pattern can be used to optimize the performance of an object by caching expensive operations or reducing network traffic.

  4. Separation of Concerns: The Proxy pattern can help to separate concerns by providing a clear boundary between the client and the object.

Cons

  1. Increased Complexity: The Proxy pattern adds an additional layer of complexity to the system, which can make it harder to understand and maintain.

  2. Overhead: The Proxy pattern can introduce additional overhead due to the additional object created and the additional method calls required.

  3. Potential for Inconsistency: The Proxy pattern can introduce the potential for inconsistency if the proxy object is not kept in sync with the real object.

Relations with other patterns

  1. Decorator Pattern
    The Proxy pattern is similar to the Decorator pattern in that both provide a way to add functionality to an object. The main difference is that the Decorator pattern adds functionality by wrapping the object with another object, while the Proxy pattern adds functionality by providing a surrogate object that acts as a stand-in for the real object.

  2. Facade Pattern: The Proxy pattern can be used as a facade to provide a simplified interface to a complex system. For example, a proxy can be used to hide the complexity of a remote object by providing a local interface that behaves in the same way as the remote object.

  3. Flyweight Pattern: The Proxy pattern can be used in conjunction with the Flyweight pattern to share objects that are expensive to create. For example, a proxy can be used to create and manage a pool of expensive objects that are shared by multiple clients.

  4. Singleton Pattern: The Proxy pattern can be used to implement the Singleton pattern by providing a proxy object that ensures only one instance of the real object is created.

The Adapter pattern is used to make two incompatible interfaces work together, the Proxy pattern is used to add a layer of indirection to control access to an object, and the Decorator pattern is used to add new functionality to an object dynamically without changing its structure. While there are some similarities between these patterns, their primary focus and usage differ.

Thanks for reading !!! Love IT Live IT Enjoy IT !!!! Happy Coding