Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
158: The Proxy Pattern
I've spent a lot of time reviewing code from junior engineers, and there is one mistake that pops up almost every time we hit the Proxy pattern: the belief that a Proxy is just a Decorator by another name. If you've already studied the Decorator pattern, you know it's all about wrapping an object to add behavior. You might look at a Proxy—which also wraps an object—and think, "Why do I need a new name for this?"
"Proxies are just Decorators with different names"
Here is why that thinking will lead you astray. A Decorator's job is to enhance the object. If I have a Coffee object, a Decorator adds Milk or Sugar. The coffee still exists, and the Decorator just adds a layer of flavor on top. The client usually provides the original object to the Decorator.
A Proxy, however, is about control. The Proxy doesn't just add "flavor"; it manages the lifecycle of the real object. In many cases, the Proxy creates the real object itself, or decides if the real object should even be accessed at all. If the Decorator is a spice rack, the Proxy is a bouncer at a club. The bouncer isn't "enhancing" the club; he's deciding if you're allowed inside.
Controlling Access and Resource Management
To see this in action, let's look at a scenario I've dealt with often: loading massive resources. Imagine you're building a gallery app that displays high-resolution 4K images. If you load 100 of these into memory the moment the app starts, your JVM is going to crash with an OutOfMemoryError before the user even sees the first screen.
This is where a Virtual Proxy comes in. Instead of the client holding the actual RealImage, they hold a ProxyImage. The real image isn't loaded until the very moment display() is called.
public interface Image {
void display();
}
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(); // This is the expensive part
}
private void loadFromDisk() {
System.out.println("Loading heavy image: " + fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
}
public class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
// Lazy Initialization: We only create the real object when absolutely necessary
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
Notice the key difference here. In a Decorator, you'd likely pass an existing Image into the ProxyImage constructor. Here, the ProxyImage owns the creation logic. The client doesn't even know RealImage exists; they just interact with the Image interface.
Different Flavors of Control
While the "Virtual Proxy" (lazy loading) is the most common, I want you to keep two other types in your toolkit:
- Protection Proxy: This is the "bouncer" I mentioned. It checks if the current user has the required permissions before calling the method on the real object.
- Remote Proxy: This is what happens under the hood with RMI or gRPC. The proxy represents an object that actually lives on a different server, handling all the networking "magic" so the client thinks they're just calling a local method.
When you're deciding between these patterns, ask yourself: "Am I adding features to an object (Decorator), or am I managing how an object is accessed (Proxy)?" If the answer is access, you've got a Proxy.
📋 Practical Task
Build a Secure Document Access Proxy
You need to implement a system where certain documents can only be read by users with "ADMIN" privileges. Create the following structure:
- An
IDocumentinterface with a methodString getContent(). - A
SensitiveDocumentclass that implementsIDocumentand returns a secret string (e.g., "Top Secret Government Data"). - A
DocumentProxyclass that implementsIDocument. This proxy should:- Take a
String userRolein its constructor. - Maintain a reference to a
SensitiveDocument. - In
getContent(), check if theuserRoleis equal to "ADMIN". - If it is, return the content from the real document.
- If it is not, return "Access Denied: Insufficient Permissions".
- Take a
Test your implementation by creating one proxy with the role "USER" and another with "ADMIN", and verify that only the admin can see the secret content.
There are no comments for now.