|
1 | 1 | --- |
2 | 2 | title: Proxy Pattern |
| 3 | +tags: |
| 4 | + - structural |
| 5 | +created: 2025-12-12 |
3 | 6 | --- |
| 7 | +## Definition |
| 8 | + |
| 9 | +**Proxy Pattern** provides a surrogate or placeholder for another object to control access to it. |
| 10 | + |
| 11 | +--- |
| 12 | +## Real-World Analogy |
| 13 | + |
| 14 | +In the previous section on the [[State Pattern]], we built a Gumball Machine example. In this pattern, we will reuse the same Gumball Machine and extend it with remote monitoring capabilities. |
| 15 | + |
| 16 | +Now, imagine that you want to control or monitor a Gumball Machine remotely, for example, checking its current state, the number of gumballs available, or its location. This can be achieved using a **Remote Proxy**. |
| 17 | + |
| 18 | +A Remote Proxy acts as a local representative for an object that exists in another Java Virtual Machine (JVM). Internally, this is implemented using **Remote Method Invocation (RMI)**. |
| 19 | + |
| 20 | +![[remote_proxy.png]] |
| 21 | + |
| 22 | +The diagram above shows the Remote Proxy setup for the Gumball Monitor. |
| 23 | + |
| 24 | +Here, `GumballMonitor` acts as the client and communicates with the `GumballMachine` through a Remote Proxy. When the monitor invokes a method, the call is forwarded to the proxy. The proxy serializes the method call and sends it to the remote server. The server then invokes the method on the real `GumballMachine` and sends the result back to the client. This indirection is the essence of the Proxy Pattern. |
| 25 | +### How This Works |
| 26 | +![[remote_proxy_working.png]] |
| 27 | + |
| 28 | +The diagram above illustrates the runtime behavior of a Remote Proxy. The flow is as follows: |
| 29 | + |
| 30 | +1. The client calls a method on the remote object, for example `doSomething()`, as if it were a local method call. |
| 31 | +2. The `RemoteProxy` intercepts the call, serializes the method name, arguments, and related metadata, and sends it to the `RemoteServer`. |
| 32 | +3. The `RemoteServer` deserializes the request and invokes the corresponding method on the real `GumballMachine`. |
| 33 | +4. Once the method execution is complete, the result is returned to the server layer. |
| 34 | +5. The server packages the result and sends it back to the `RemoteProxy`. |
| 35 | +6. The proxy deserializes the response and returns it to the client (`GumballMonitor`). |
| 36 | +### Java RMI |
| 37 | + |
| 38 | +RMI stands for **Remote Method Invocation**. It allows a client to invoke methods on an object that resides in a different JVM. Although RMI is considered an older technology and is not commonly used in modern systems, it is still very useful for learning and understanding the Remote Proxy pattern. |
| 39 | + |
| 40 | +In RMI terminology, the client-side helper is called a _stub_, and the server-side helper is called a _skeleton_. The diagram below illustrates this relationship. |
| 41 | + |
| 42 | +![[RMI_nomenclature.png]] |
| 43 | + |
| 44 | +Next, we will implement the Gumball Monitor using the code from the [[State Pattern]]. |
| 45 | + |
| 46 | +--- |
| 47 | +## Design |
| 48 | + |
| 49 | +The class diagram for the Proxy Pattern is straightforward. The `Subject` interface defines the operations that are implemented by both the `RealSubject` and the `Proxy`. The client interacts only with the `Subject` interface, remaining unaware of whether it is working with the real object or the proxy. |
| 50 | + |
| 51 | +```mermaid |
| 52 | +classDiagram |
| 53 | + direction LR |
| 54 | +
|
| 55 | + class Subject { |
| 56 | + <<interface>> |
| 57 | + +request() |
| 58 | + } |
| 59 | +
|
| 60 | + class RealSubject { |
| 61 | + +request() |
| 62 | + } |
| 63 | +
|
| 64 | + class Proxy { |
| 65 | + -realSubject : RealSubject |
| 66 | + +request() |
| 67 | + } |
| 68 | +
|
| 69 | + Subject <|.. RealSubject |
| 70 | + Subject <|.. Proxy |
| 71 | + Proxy *-- RealSubject |
| 72 | +``` |
| 73 | + |
| 74 | +--- |
| 75 | +## Implementation in Java |
| 76 | + |
| 77 | +We start by creating the `ProxyRemote` interface, which will be implemented by both the proxy and the `GumballMachine`. This interface plays the same role as the `Subject` interface shown in the design section. |
| 78 | + |
| 79 | +Extending `Remote` marks this interface as a remote interface in RMI. |
| 80 | +```java title="ProxyRemote.java" |
| 81 | +import proxy.state.State; |
| 82 | +import java.rmi.Remote; |
| 83 | +import java.rmi.RemoteException; |
| 84 | + |
| 85 | +public interface ProxyRemote extends Remote { |
| 86 | + int getCount() throws RemoteException; |
| 87 | + String getLocation() throws RemoteException; |
| 88 | + State getState() throws RemoteException; |
| 89 | +} |
| 90 | +``` |
| 91 | + |
| 92 | +The `getState()` method returns a custom `State` object. For RMI to work correctly, all non-primitive return types must be serializable. Primitive types and standard Java types are already handled by the RMI framework. |
| 93 | + |
| 94 | +To make `State` serializable, we extend the `Serializable` interface from the `java.io` package. |
| 95 | +```java title="State.java" |
| 96 | +import java.io.Serializable; |
| 97 | + |
| 98 | +public interface State extends Serializable { |
| 99 | + void insertQuarter(); |
| 100 | + void ejectQuarter(); |
| 101 | + void dispense(); |
| 102 | + void turncrank(); |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +At this point, the custom data type can be transferred across the network. |
| 107 | + |
| 108 | +However, each concrete `State` implementation holds a reference to the `GumballMachine` so that it can trigger state transitions. We do not want the entire `GumballMachine` object to be serialized and sent over the network along with the state. |
| 109 | + |
| 110 | +To prevent this, the reference to `GumballMachine` is marked as `transient` in each state class. |
| 111 | + |
| 112 | +```java title="HasQuarterState.java" |
| 113 | +class HasQuarterState implements State { |
| 114 | + private static final long serialVersionUID = 2L; |
| 115 | + private final transient GumballMachine gumballMachine; |
| 116 | + // remaining code |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +```java title="NoQuarterState.java" |
| 121 | +class NoQuarterState implements State { |
| 122 | + private static final long serialVersionUID = 2L; |
| 123 | + private final transient GumballMachine gumballMachine; |
| 124 | + // remaining code |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +```java title="SoldOutState.java" |
| 129 | +class SoldOutState implements State { |
| 130 | + private static final long serialVersionUID = 2L; |
| 131 | + private final transient GumballMachine gumballMachine; |
| 132 | + // remaining code |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +```java title="SoldState.java" |
| 137 | +class SoldState implements State { |
| 138 | + private static final long serialVersionUID = 2L; |
| 139 | + private final transient GumballMachine gumballMachine; |
| 140 | + // remaining code |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +```java title="WinnerState.java" |
| 145 | +class WinnerState implements State { |
| 146 | + private static final long serialVersionUID = 2L; |
| 147 | + private final transient GumballMachine gumballMachine; |
| 148 | + // remaining code |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +The `transient` keyword tells the JVM not to include this field during serialization. When the object is deserialized, the transient field is initialized with its default value, avoiding unnecessary data transfer. |
| 153 | + |
| 154 | +Next, the `GumballMachine` is exposed as a remote service. To do this, it implements the `ProxyRemote` interface and extends `UnicastRemoteObject`, which provides the RMI infrastructure. |
| 155 | + |
| 156 | +```java title="GumballMachine.java" |
| 157 | +public class GumballMachine extends UnicastRemoteObject implements ProxyRemote { |
| 158 | + private static final long serialVersionUID = 2L; |
| 159 | + |
| 160 | + // other private variables |
| 161 | + |
| 162 | + public GumballMachine(String location, int numberOfGumballs) throws RemoteException { |
| 163 | + // initialization and state setup |
| 164 | + } |
| 165 | +} |
| 166 | +``` |
| 167 | + |
| 168 | +Here, extending `UnicastRemoteObject` allows the object to receive remote method calls, while implementing `ProxyRemote` exposes the methods that clients are allowed to invoke. |
| 169 | +### Registering with the RMI Registry |
| 170 | + |
| 171 | +The next step is to register the `GumballMachine` with the RMI Registry so that clients can discover it. |
| 172 | +```java title="RemoteServer.java" |
| 173 | +public class RemoteServer { |
| 174 | + public static void main(String[] args) { |
| 175 | + Remote remote; |
| 176 | + int count = 5; |
| 177 | + String location = "localhost"; |
| 178 | + |
| 179 | + try { |
| 180 | + remote = new GumballMachine(location, count); |
| 181 | + Naming.rebind("//" + location + "/gumballmachine", remote); |
| 182 | + } catch (Exception e) { |
| 183 | + e.printStackTrace(); |
| 184 | + } |
| 185 | + } |
| 186 | +} |
| 187 | +``` |
| 188 | + |
| 189 | +This makes the remote object available at `rmi://localhost/gumballmachine`. |
| 190 | +Before running the server, the RMI Registry must be started. |
| 191 | +```bash |
| 192 | +start rmiregistry 1099 |
| 193 | +``` |
| 194 | +This command starts the RMI Registry on port `1099`. |
| 195 | +After starting the registry, run `RemoteServer.java` to register the `GumballMachine`. |
| 196 | +### Creating the Monitor (Client-Side Proxy) |
| 197 | + |
| 198 | +The monitor class allows us to observe the `GumballMachine` through the exposed remote methods. It interacts only with the `ProxyRemote` interface, which makes it act as a proxy client. |
| 199 | +```java title="RemoteMonitor.java" |
| 200 | +public class RemoteMonitor { |
| 201 | + |
| 202 | + private final ProxyRemote gumballMachine; |
| 203 | + |
| 204 | + public RemoteMonitor(ProxyRemote machine) { |
| 205 | + this.gumballMachine = machine; |
| 206 | + } |
| 207 | + |
| 208 | + public void report() { |
| 209 | + try { |
| 210 | + System.out.println("Gumball Machine: " + gumballMachine.getLocation()); |
| 211 | + System.out.println("Gumball Machine Inventory: " + gumballMachine.getCount() + " gumballs"); |
| 212 | + System.out.println("Current State: " + gumballMachine.getState()); |
| 213 | + } catch (RemoteException re) { |
| 214 | + re.printStackTrace(); |
| 215 | + } |
| 216 | + } |
| 217 | +} |
| 218 | +``` |
| 219 | +This class typically runs on a different machine from the server. |
| 220 | +### Creating the Client (RMI Stub User) |
| 221 | + |
| 222 | +The client looks up the remote object from the RMI Registry and uses it through the `ProxyRemote` interface. |
| 223 | +```java title="RemoteTest.java" |
| 224 | +public class RemoteTest { |
| 225 | + public static void main(String[] args) { |
| 226 | + |
| 227 | + String location = "rmi://localhost/gumballmachine"; |
| 228 | + RemoteMonitor monitor; |
| 229 | + |
| 230 | + try { |
| 231 | + ProxyRemote remote = (ProxyRemote) Naming.lookup(location); |
| 232 | + monitor = new RemoteMonitor(remote); |
| 233 | + } catch (Exception e) { |
| 234 | + throw new RuntimeException(e); |
| 235 | + } |
| 236 | + |
| 237 | + monitor.report(); |
| 238 | + } |
| 239 | +} |
| 240 | +``` |
| 241 | +This code retrieves the proxy reference and allows the client to interact with the remote `GumballMachine` as if it were local. |
| 242 | + |
| 243 | +_Output:_ |
| 244 | +```txt |
| 245 | +Gumball Machine: localhost |
| 246 | +Gumball Machine Inventory: 5 gumballs |
| 247 | +Current State: NoQuarterState |
| 248 | +``` |
| 249 | + |
| 250 | +--- |
| 251 | +## Implementation in Java – 2 |
| 252 | + |
| 253 | +In the previous implementation, we used **Remote Method Invocation (RMI)** to demonstrate the Proxy Pattern. In this section, we will implement a simpler proxy based on the [[#Design]] shown earlier. This example demonstrates a **Cache (Virtual) Proxy**. |
| 254 | + |
| 255 | +```java title="Subject.java" |
| 256 | +interface Subject { |
| 257 | + void request(); |
| 258 | +} |
| 259 | +``` |
| 260 | + |
| 261 | +This interface defines the common contract that both the real object and the proxy must follow. |
| 262 | +```java title="RealSubject.java" |
| 263 | +class RealSubject implements Subject { |
| 264 | + private String resource; |
| 265 | + |
| 266 | + public RealSubject(String resource) { |
| 267 | + this.resource = resource; |
| 268 | + this.loadResource(); |
| 269 | + } |
| 270 | + |
| 271 | + private void loadResource() { |
| 272 | + System.out.println("Loading the Resource: " + this.resource); |
| 273 | + } |
| 274 | + |
| 275 | + @Override |
| 276 | + public void request() { |
| 277 | + System.out.println("Requested: " + this.resource); |
| 278 | + } |
| 279 | +} |
| 280 | +``` |
| 281 | +The `RealSubject` represents the actual object that performs the expensive operation of loading a resource. |
| 282 | +```java title="Proxy.java" |
| 283 | +class Proxy implements Subject { |
| 284 | + private Subject subject; |
| 285 | + private String resource; |
| 286 | + |
| 287 | + public Proxy(String resource) { |
| 288 | + this.resource = resource; |
| 289 | + } |
| 290 | + |
| 291 | + @Override |
| 292 | + public void request() { |
| 293 | + if (subject == null) { |
| 294 | + subject = new RealSubject(resource); |
| 295 | + } |
| 296 | + subject.request(); |
| 297 | + } |
| 298 | +} |
| 299 | +``` |
| 300 | +The proxy delays the creation of the `RealSubject` until it is actually needed and then delegates subsequent calls directly to it. |
| 301 | +```java title="ProxyPattern.java" |
| 302 | +public class ProxyPattern { |
| 303 | + public static void main(String[] args) { |
| 304 | + Subject subject = new Proxy("new pdf download"); |
| 305 | + subject.request(); |
| 306 | + subject.request(); |
| 307 | + } |
| 308 | +} |
| 309 | +``` |
| 310 | +_Output:_ |
| 311 | +```txt |
| 312 | +Loading the Resource: new pdf download |
| 313 | +Requested: new pdf download |
| 314 | +Requested: new pdf download |
| 315 | +``` |
| 316 | +On the first request, the resource is loaded. On subsequent requests, the already-created object is reused. |
| 317 | + |
| 318 | +--- |
| 319 | +## Types of Proxy |
| 320 | + |
| 321 | +There are several commonly used types of proxies: |
| 322 | +### Remote Proxy: |
| 323 | +With the Remote Proxy, the proxy acts as a local representative for an object that lives in a different JVM. A method call on the proxy results in the call being transferred over the wire and invoked remotely, and the result being returned back to the proxy and then to the client. |
| 324 | +### Virtual Proxy: |
| 325 | +The Virtual Proxy acts as a representative for an object that may be expensive to create. The virtual Proxy often defers the creation of the object until it is needed; the Virtual Proxy also acts as a surrogate for the object before and while it is being created. After that, the proxy delegates requests directly to the RealSubject. |
| 326 | +**Example**: Loading a Large Image only when it is displayed on the Screen |
| 327 | +### Protection Proxy: |
| 328 | +A Protection Proxy is used to control access to an object. Before forwarding a request to the Real Subject, the proxy checks whether the user has the required permissions. If the user is authorized, the request is allowed; otherwise it is blocked. |
| 329 | +### Cache Proxy: |
| 330 | +A Cache Proxy is used to store the results of expensive operations. When a request is made, the proxy first checks whether the result is already stored in the cache. If it is, the cached result is returned immediately. If not, the request is sent to the real object, and the result is saved for future use. |
| 331 | +**Example:** Caching API responses or database query results. |
| 332 | + |
| 333 | +Other variants include firewall proxies, synchronization proxies, and smart proxies. |
| 334 | + |
| 335 | +--- |
| 336 | +## Real-World Examples |
| 337 | + |
| 338 | +Proxy patterns are widely used in modern frameworks and applications, often without us explicitly realizing it. |
| 339 | + |
| 340 | +Object-Relational Mapping (ORM) frameworks such as Entity Framework Core in .NET and Hibernate in Java use proxies to manage database interactions and lazy loading of entities. |
| 341 | + |
| 342 | +Proxies are also heavily used in Dependency Injection frameworks, where objects are wrapped to add cross-cutting concerns such as logging, security, or transaction management. |
| 343 | + |
| 344 | +--- |
| 345 | +## Design Principles: |
| 346 | + |
| 347 | +- **Encapsulate What Varies** - Identify the parts of the code that are going to change and encapsulate them into separate class just like the Strategy Pattern. |
| 348 | +- **Favor Composition Over Inheritance** - Instead of using inheritance on extending functionality, rather use composition by delegating behavior to other objects. |
| 349 | +- **Program to Interface not Implementations** - Write code that depends on Abstractions or Interfaces rather than Concrete Classes. |
| 350 | +- **Strive for Loosely coupled design between objects that interact** - When implementing a class, avoid tightly coupled classes. Instead, use loosely coupled objects by leveraging abstractions and interfaces. This approach ensures that the class does not heavily depend on other classes. |
| 351 | +- **Classes Should be Open for Extension But closed for Modification** - Design your classes so you can extend their behavior without altering their existing, stable code. |
| 352 | +- **Depend on Abstractions, Do not depend on concrete class** - Rely on interfaces or abstract types instead of concrete classes so you can swap implementations without altering client code. |
| 353 | +- **Talk Only To Your Friends** - An object may only call methods on itself, its direct components, parameters passed in, or objects it creates. |
| 354 | +- **Don't call us, we'll call you** - This means the framework controls the flow of execution, not the user’s code (Inversion of Control). |
| 355 | +- **A class should have only one reason to change** - This emphasizes the Single Responsibility Principle, ensuring each class focuses on just one functionality. |
0 commit comments