The proxy design pattern provides a substitute or placeholder for another object to control its access. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object.
// Subject Interface
interface Vehicle {
void drive(String location);
}
// Real Subject
class RealAIPoweredVehicle implements Vehicle {
@Override
public void drive(String location) {
System.out.println("RealAIPoweredVehicle is being driven. Arrival destination: " + location);
}
}
class RequestValidator {
private static List<String> carFreeLocation;
static {
carFreeLocation = new ArrayList<>();
carFreeLocation.add("Pedestrian Zone A");
carFreeLocation.add("Pedestrian Zone B");
carFreeLocation.add("Pedestrian Zone C");
}
public static void validateLocation(String location) throws IllegalArgumentException {
if (carFreeLocation.contains(location)) {
throw new IllegalArgumentException(location + " doesn't allow arrival by the means of a vehicle"
+ "\n" + "Please select another location!");
}
}
}
// Proxy
class AIPoweredVehicleProxy implements Vehicle {
private RealAIPoweredVehicle realAIPoweredVehicle;
public AIPoweredVehicleProxy() {
this.realAIPoweredVehicle = new RealAIPoweredVehicle();
}
@Override
public void drive(String location) {
// Additional functionality or control can be added here
RequestValidator.validateLocation(location);
realAIPoweredVehicle.drive(location);
}
}
public class Main {
public static void main(String[] args) {
AIPoweredVehicleProxy vehicleProxy = new AIPoweredVehicleProxy();
try {
System.out.println("Proxy: Checking if arrival destination is prohibited");
vehicleProxy.drive("Zone A");
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
// Proxy: Checking if arrival destination is prohibited
// RealAIPoweredVehicle is being driven. Arrival destination: Zone A
try {
System.out.println("Proxy: Checking if arrival destination is prohibited");
vehicleProxy.drive("Pedestrian Zone C");
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
// Proxy: Checking if arrival destination is prohibited
// Pedestrian Zone C doesn't allow arrival by the mean of a vehicle
// Please select another location!
}
}
In this example, the Proxy design pattern is demonstrated using the concept of an AI-Powered vehicle. The Vehicle
interface defines the common methods that all vehicles should implement. The RealAIPoweredVehicle
class is the actual implementation of the Vehicle
interface. It represents a real AI-powered vehicle that can be driven to a specific location.
The AIPoweredVehicleProxy
class serves as a proxy for the RealAIPoweredVehicle
object. It also implements the Vehicle
interface and contains an instance of the RealAIPoweredVehicle
class.
When the drive(String location)
method is called on the AIPoweredVehicleProxy
object, it performs some additional functionality before forwarding the request to the real object. First, it lets the RequestValidator
class handles any business logic, in this case, RequestValidator
class has a static list carFreeLocation
that represents locations where vehicles are not allowed to arrive. This list is used for additional control. Once validated, request will be forwarded.
In the Main
class (can be though of as the Client
), we create an instance of the AIPoweredVehicleProxy
and call its drive(String location)
method. The proxy intercepts the method call, checks if the location is prohibited, and either displays a message or delegates the actual driving to the real AI-powered vehicle.
The Proxy pattern is applied in this example to add additional functionality and control before delegating the request to the real object. The proxy class acts as a surrogate for the real AI-powered vehicle and provides a way to perform checks on the destination location before driving. It allows us to control the behavior of the real object without modifying its implementation, providing an extra layer of indirection and encapsulation.
Use the Proxy Pattern to utilize Lazy Initialization (Virtual Proxy). This is when you have a heavyweight service object that wastes system resources by being always up, even though you only need it from time to time.
- Instead of creating the object when the app launches, you can delay the object’s initialization to a time when it’s really needed. This would save memory footprint.
- Access control (protection proxy). This is when you want only specific clients to be able to use the service object; for instance, when your objects are crucial parts of an operating system and clients are various launched applications (including malicious ones). The proxy can pass the request to the service object only if the client’s credentials match some criteria.
Use the Proxy Pattern when you want to abstract away the underlying complexity of the real object by providing a simpler interface.
- Instead of directly interacting with the real object, the client code interacts with the proxy object, which in turn handles the interaction with the real object. By using the
Proxy
object, the client code deals with a simpler and more abstracted interface. It can work with theProxy
object in the same way it would work with the real object, as they both adhere to the same interface.- In the case of Networking (routing, sending, receiving requests ...), Instead of worrying about all the details of Networking. The Proxy Pattern allows for a clear separation of concerns.
- The proxy object takes care of additional functionality, such as access control, logging, caching, or remote communication, while the real object focuses on its primary responsibility.
- Logging requests (logging proxy). This is when you want to keep a history of requests to the service object. The proxy can log each request before passing it to the service.
- Caching request results (caching proxy). This is when you need to cache results of client requests and manage the life cycle of this cache, especially if results are quite large. The proxy can implement caching for recurring requests that always yield the same results. The proxy may use the parameters of requests as the cache keys.
Use the Proxy Pattern for better object lifecycle management.
- The
Proxy
object can keep track of the clients that have obtained a reference to the service object or its results, allowing it to make decisions regarding the lifecycle of the underlying object.- Tracking Client References: The
Proxy
maintains a list of clients that have obtained a reference to the service object or its results. It keeps track of which clients are currently active. - Periodic Client Check: The proxy periodically checks the client list to determine if there are still active clients. If the client list becomes empty, it indicates that no clients are using the service object. This is the indication for the proxy to take action. (eg. Free service objects in the Proxy's cache that no clients have reference to ...)
- Reusing Unchanged Objects: Additionally, the proxy can track whether the client has modified the service object. If the objects remain unchanged, the proxy can reuse them for other clients, avoiding the need to create new instances of the service object.
- Tracking Client References: The