The Proxy pattern is a structural design pattern that provides a surrogate or placeholder for another object. It acts as an intermediary, controlling the access to the real object and allowing additional functionality to be added before or after accessing it.
In software development, the Proxy pattern is often employed when you want to control the interaction with an object in a flexible and transparent manner. By using a proxy, you can encapsulate the complexity of accessing the real object and manipulate its behavior without the client code being aware of it.
Proxy Pattern Applicability
Here are some common situations where the Proxy pattern can be beneficial:
- Remote Access: When dealing with remote objects or services, the Proxy pattern can be used to provide a local representative (proxy) that handles the communication and marshaling of data between the client and the remote object. This allows for transparency in accessing remote resources.
- Lazy Initialization: If creating and initializing an object is expensive or time-consuming, the Proxy pattern can be employed to delay the creation of the real object until it is actually needed. This is useful when dealing with large objects, database connections, or resource-intensive operations.
- Access Control: Proxies can be used to enforce access control mechanisms by acting as gatekeepers to the real object. The proxy can check the client’s credentials, permissions, or any other authorization criteria before allowing access to the real object. This is particularly useful in scenarios where fine-grained access control is required.
- Caching: The Proxy pattern can be utilized to implement caching mechanisms. The proxy can cache the results of expensive operations performed by the real object and return the cached results for subsequent requests. This improves performance by reducing the need for repeated expensive computations or data fetching.
- Logging and Audit Trails: Proxies can be employed to add logging or audit trail functionality around the real object. The proxy can capture method invocations, parameter values, and execution details, providing valuable information for debugging, analysis, or security auditing purposes.
- Performance Optimization: By using a proxy, you can implement optimizations such as lazy loading, where data is fetched only when required, or implement flyweight patterns to conserve resources by sharing common data among multiple objects.
- Error Handling and Exception Management: Proxies can handle exceptions or errors that occur during the execution of the real object’s methods. They can catch and handle exceptions gracefully, provide fallback behavior, or return default values, ensuring that the client code remains robust and resilient.
Real World Analogy
A real-world analogy that can help understand the Proxy pattern is that of a personal assistant or secretary.
Imagine you are a busy executive and you have a personal assistant or secretary who acts as a proxy between you and the outside world. Here’s how the analogy aligns with the Proxy pattern:
- Controlled Access: Your personal assistant controls access to you. They filter phone calls, emails, and appointments, only allowing important and relevant ones to reach you. Similarly, a proxy controls access to the real object and decides which requests to forward.
- Additional Functionality: Your personal assistant can perform additional tasks before or after interacting with you. They may schedule meetings, organize your calendar, or prepare documents on your behalf. In the same way, a proxy can add extra functionality such as caching, logging, or security checks before or after accessing the real object.
- Indirect Interaction: When someone wants to meet with you, they typically go through your personal assistant. They don’t directly interact with you unless it is necessary. Similarly, clients interact with the proxy object instead of the real object, unless there is a specific need to access the real object directly.
- Resource Optimization: Your personal assistant helps optimize your time and resources. They handle routine tasks, allowing you to focus on more important matters. Likewise, a proxy can perform tasks like lazy loading or resource-intensive operations only when needed, optimizing the utilization of system resources.
- Security and Protection: Your personal assistant acts as a buffer, shielding you from unwanted or potentially harmful interactions. They verify the credibility of callers and filter out spam or irrelevant messages. Similarly, a proxy can enforce security measures, authenticate clients, or restrict access to sensitive operations in order to protect the real object.
Proxy Design Pattern Implementation
The Proxy pattern involves three main components:
- Subject: This defines the common interface or abstract class that both the real object and the proxy implement. It declares the methods that the client can use to access the real object.
- Real Subject: This represents the actual object that the proxy is a surrogate for. It provides the core functionality that the client desires.
- Proxy: This acts as a substitute for the real subject. It implements the same interface as the subject and maintains a reference to the real object. The proxy controls the access to the real object and can perform additional tasks before or after delegating calls to it.
Example
// Subject interface
public interface PaymentService {
void pay(double amount);
}
// RealSubject class
public class PaymentServiceImpl implements PaymentService {
@Override
public void pay(double amount) {
System.out.println("Payment of $" + amount + " processed.");
}
}
// Proxy class
public class PaymentServiceProxy implements PaymentService {
private PaymentService paymentService;
@Override
public void pay(double amount) {
if (paymentService == null) {
paymentService = new PaymentServiceImpl();
}
// Additional functionality before payment
System.out.println("Verifying payment details...");
// Delegating the payment to the real service
paymentService.pay(amount);
// Additional functionality after payment
System.out.println("Sending payment confirmation email...");
}
}
// Client code
public class Client {
public static void main(String[] args) {
PaymentService paymentService = new PaymentServiceProxy();
paymentService.pay(100.00);
}
}
JavaIn this example, we have a PaymentService
interface that declares the common pay()
method for both the real payment service (PaymentServiceImpl
) and the proxy (PaymentServiceProxy
). The PaymentServiceImpl
represents the actual payment service implementation that processes the payment.
The PaymentServiceProxy
class acts as a proxy for the real payment service. It lazily initializes the real service (PaymentServiceImpl
) when the pay()
method is called for the first time. The proxy performs additional functionality before and after delegating the payment to the real service. In this case, it verifies payment details before processing the payment and sends a payment confirmation email afterward.
By using the Proxy pattern, we can introduce additional functionality (verification and confirmation email) without modifying the real payment service. The proxy provides a transparent layer that controls access to the real service and adds the desired behavior around it.
proxy pattern & design principles
The Proxy design pattern aligns with several fundamental design principles that promote modular, flexible, and maintainable software architectures. Here are some design principles that the Proxy pattern relates to:
- Single Responsibility Principle (SRP): The Proxy pattern adheres to the SRP by separating the concerns of access control, additional functionality, and object creation from the core business logic of the real object. The proxy handles these responsibilities, while the real object focuses solely on its primary functionality.
- Open/Closed Principle (OCP): The Proxy pattern supports the OCP by allowing you to extend the behavior of an object without modifying its code. You can introduce new proxy types with different functionality or access control rules without changing the real object, thus ensuring its stability.
- Liskov Substitution Principle (LSP): The Proxy pattern maintains the LSP as the proxy and the real object implement the same interface or inherit from the same base class. Clients can seamlessly use a proxy in place of the real object without affecting the correctness of the program.
- Dependency Inversion Principle (DIP): The Proxy pattern embraces the DIP by programming to an interface. Clients depend on the abstraction provided by the subject interface rather than the concrete implementation of the real object or proxy. This allows for flexibility, extensibility, and easy substitution of different implementations.
- Composition over Inheritance: The Proxy pattern favors composition over inheritance, as the proxy contains an instance of the real object rather than inheriting its behavior. This promotes flexible object composition and allows for dynamic runtime behavior changes.
- Separation of Concerns (SoC): The Proxy pattern supports the SoC principle by separating the concerns of object access, additional functionality, and object creation. Each concern is encapsulated within the respective proxy class, resulting in a more modular and maintainable codebase.
Common Bad Implementations of Proxy Pattern
- Direct Invocation of Real Object: This defeats the purpose of using a proxy as an intermediary and bypasses any additional functionality or access control that the proxy is supposed to provide.
- No Interface or Inheritance: Another incorrect implementation is when the proxy class does not implement the same interface or inherit from the same base class as the real object. This breaks the contract between the client code and the proxy, making it incompatible with the expected usage of the Proxy pattern.
- Hard-Coded Object Instantiation: In some cases, developers mistakenly instantiate the real object directly within the proxy class, instead of relying on dynamic instantiation or dependency injection. This tightly couples the proxy to a specific implementation of the real object, making it less flexible and harder to substitute with different implementations.
- Missing Lazy Initialization: One important aspect of the Proxy pattern is lazy initialization, where the real object is created only when it is actually needed. A wrong implementation would instantiate the real object upfront, regardless of whether it will be used or not, resulting in unnecessary resource consumption and potential performance issues.
- Ignoring Additional Functionality: Proxies should offer additional functionality before or after delegating calls to the real object. However, a wrong implementation may neglect to include this additional functionality, essentially creating a pass-through proxy that provides no value beyond the direct access to the real object.
- Inadequate Error Handling: Proxies should handle exceptions and errors appropriately, ensuring that they are caught, handled, or propagated as necessary. A common mistake is to overlook proper error handling within the proxy, leading to unhandled exceptions or improper error propagation, which can result in unexpected behavior or system instability.
Known Usages
- java.lang.reflect.Proxy
- Apache Commons Proxy
- Mocking frameworks Mockito, Powermock, EasyMock
Popular Related Terms
Although, these are typically related to system design, but the underlying concept remains same – acting on someone else’s behalf.
A forward proxy and a reverse proxy are two different types of proxies that serve distinct purposes in network communication:
- Forward Proxy: A forward proxy, also known as an “outbound proxy,” sits between a client and the internet. When a client makes a request to access a resource on the internet, it first goes through the forward proxy. The proxy then forwards the request on behalf of the client to the destination server. The server sees the request as coming from the proxy, not the original client. Forward proxies are commonly used for purposes such as anonymizing client identities, controlling access to certain websites, or caching frequently accessed resources.
- Reverse Proxy: A reverse proxy, also referred to as an “inbound proxy,” operates on the server side of a network architecture. It sits between the client and one or more servers, acting as an intermediary. When a client sends a request to access a particular server, it goes through the reverse proxy first. The reverse proxy then forwards the request to the appropriate server, based on various criteria such as load balancing, request routing, or caching. The response from the server is then returned to the client through the reverse proxy. Reverse proxies are often used to improve performance, scalability, and security by distributing incoming client requests among multiple servers and handling tasks such as SSL termination, content caching, or application-level security filtering.