Introduction
The Proxy design pattern is a structural pattern that provides a surrogate or placeholder for another object to control access to it. This pattern is particularly useful when you need to add an additional level of control over an object, such as lazy initialization, access control, logging, or caching.
Key Concepts
- Proxy Object: Acts as an intermediary between the client and the real object.
- Real Subject: The actual object that the proxy represents.
- Client: The entity that interacts with the proxy to perform operations on the real subject.
Types of Proxies
- Virtual Proxy: Controls access to a resource that is expensive to create. It creates the resource only when it is needed.
- Remote Proxy: Represents an object that exists in a different address space. It handles communication between the client and the remote object.
- Protection Proxy: Controls access to the real object by checking permissions.
- Smart Proxy: Adds additional behavior when an object is accessed, such as reference counting or logging.
Structure
The Proxy pattern involves the following participants:
- Subject: Defines the common interface for RealSubject and Proxy.
- RealSubject: Implements the Subject interface and represents the actual object.
- Proxy: Implements the Subject interface and controls access to the RealSubject.
UML Diagram
+---------+ +---------+ +---------+ | Client |------->| Proxy |------->|RealSubject| +---------+ +---------+ +---------+
Example
Let's consider an example where we use a Virtual Proxy to control access to a resource-intensive object.
Step-by-Step Implementation
- Define the Subject Interface:
- Implement the RealSubject:
public class RealImage implements Image { private String fileName; public RealImage(String fileName) { this.fileName = fileName; loadFromDisk(fileName); } private void loadFromDisk(String fileName) { System.out.println("Loading " + fileName); } @Override public void display() { System.out.println("Displaying " + fileName); } }
- Implement the Proxy:
public class ProxyImage implements Image { private RealImage realImage; private String fileName; public ProxyImage(String fileName) { this.fileName = fileName; } @Override public void display() { if (realImage == null) { realImage = new RealImage(fileName); } realImage.display(); } }
- Client Code:
public class ProxyPatternDemo { public static void main(String[] args) { Image image = new ProxyImage("test_image.jpg"); // Image will be loaded from disk image.display(); System.out.println(""); // Image will not be loaded from disk image.display(); } }
Explanation
- The
Image
interface defines the common interface forRealImage
andProxyImage
. - The
RealImage
class implements theImage
interface and represents the actual object that is resource-intensive to create. - The
ProxyImage
class also implements theImage
interface and controls access to theRealImage
object. It creates theRealImage
object only when it is needed. - The
ProxyPatternDemo
class demonstrates the use of the Proxy pattern. The first call todisplay()
loads the image from disk, while the second call does not, as the image is already loaded.
Practical Exercises
Exercise 1: Implement a Protection Proxy
Task: Implement a Protection Proxy that controls access to a BankAccount
object based on user roles.
- Define the Subject Interface:
public interface BankAccount { void deposit(double amount); void withdraw(double amount); double getBalance(); }
- Implement the RealSubject:
public class RealBankAccount implements BankAccount { private double balance; @Override public void deposit(double amount) { balance += amount; } @Override public void withdraw(double amount) { if (balance >= amount) { balance -= amount; } else { System.out.println("Insufficient funds"); } } @Override public double getBalance() { return balance; } }
- Implement the Proxy:
public class BankAccountProxy implements BankAccount { private RealBankAccount realBankAccount; private String userRole; public BankAccountProxy(String userRole) { this.realBankAccount = new RealBankAccount(); this.userRole = userRole; } @Override public void deposit(double amount) { if ("Admin".equals(userRole)) { realBankAccount.deposit(amount); } else { System.out.println("Access Denied: Only Admin can deposit"); } } @Override public void withdraw(double amount) { if ("Admin".equals(userRole)) { realBankAccount.withdraw(amount); } else { System.out.println("Access Denied: Only Admin can withdraw"); } } @Override public double getBalance() { return realBankAccount.getBalance(); } }
- Client Code:
public class ProtectionProxyDemo { public static void main(String[] args) { BankAccount adminAccount = new BankAccountProxy("Admin"); BankAccount userAccount = new BankAccountProxy("User"); adminAccount.deposit(1000); userAccount.deposit(1000); adminAccount.withdraw(500); userAccount.withdraw(500); System.out.println("Admin Account Balance: " + adminAccount.getBalance()); System.out.println("User Account Balance: " + userAccount.getBalance()); } }
Solution Explanation
- The
BankAccount
interface defines the common interface forRealBankAccount
andBankAccountProxy
. - The
RealBankAccount
class implements theBankAccount
interface and represents the actual bank account. - The
BankAccountProxy
class also implements theBankAccount
interface and controls access to theRealBankAccount
object based on user roles. - The
ProtectionProxyDemo
class demonstrates the use of the Protection Proxy. TheAdmin
role can deposit and withdraw money, while theUser
role cannot.
Common Mistakes and Tips
-
Mistake: Not implementing the same interface in both the Proxy and RealSubject.
- Tip: Ensure that both the Proxy and RealSubject implement the same interface to maintain consistency.
-
Mistake: Creating the RealSubject object in the Proxy constructor.
- Tip: Use lazy initialization to create the RealSubject object only when it is needed.
-
Mistake: Overcomplicating the Proxy logic.
- Tip: Keep the Proxy logic simple and focused on controlling access to the RealSubject.
Conclusion
The Proxy design pattern is a powerful tool for controlling access to objects. By understanding and implementing different types of proxies, such as Virtual, Remote, Protection, and Smart proxies, you can add an additional layer of control and functionality to your applications. Practice implementing proxies in various scenarios to reinforce your understanding of this pattern.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What are Design Patterns?
- History and Origin of Design Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
Module 3: Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Module 5: Application of Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring Using Design Patterns
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Design Patterns in Agile Development