Proxy Design Pattern Tutorial In Java

Introduction

The proxy pattern is a structural design pattern. It is used to provide a surrogate or placeholder object for another object, which can be used to control access to the original object or to add additional functionality.

Use Cases

Use the proxy pattern when:

  • You need to control access to existing objects
  • Defer the creation of the original object until it is actually needed (lazy loading)
  • Adding additional functionality to an object without modifying it

The client (the one who needs the functionalities of the original object) doesn’t need to know the existence of the proxy object.

Real-life examples

Imagine you are a mafia boss who needs to declare the business you operate every month to the tax bureau. Being a mafia boss, this is a real nuisance to you. So, you hire an assistant, make him remember the list of business and he does the declaration for you. Every time you expand your operations, you update him with the new list. Every time the bureau comes, they don’t ask you but the assistant to get the answer.

If you find this pattern similar, yes it is. Caching can be considered an implementation of the proxy pattern.

This procedure is killing you
This procedure is killing you
Proxy pattern in real life scenario
So you hired an assistant

Code Examples

Using the Proxy pattern for controlling access to objects

Here is an example of using the proxy pattern in Java code.

// The SecretVault class is the class whose methods we want to control access to.
public class SecretVault {
  public void revealSecret() {
    // Some code that performs some action.
  }
}

// The proxy class is a class that controls access to the SecretVault class.
public class SecretVaultProxy {
  private SecretVault vault;

  public SecretVaultProxy(SecretVault vault) {
    this.vault = vault;
  }

  // The proxy class provides a method that allows clients to access the
  // SecretVault's revealSecret() method, but only if the client has the appropriate
  // permission.
  public void revealSecret() {
    // Check if the client has the appropriate permission.
    if (hasPermission()) {
      // If the client has the permission, then invoke the vault's
      // revealSecret() method.
      vault.revealSecret();
    } else {
      // If the client does not have the permission, then do nothing.
      // Alternatively, you could throw an exception or return an error message.
    }
  }

  // Method for checking if the client has the appropriate permission.
  private boolean hasPermission() {
    // Check if the client has the permission.
    // In this example, we'll just return true to indicate that the client
    // has the permission. In a real application, you would need to implement
    // the logic for checking the client's permissions.
    return true;
  }
}

In this example, the SecretVaultProxy class acts as a proxy for the SecretVault class. The SecretVaultProxy class provides a revealSecret() method that allows clients to access the SecretVault class’s revealSecret() method, but only if the client has the appropriate permission. This is an example of how the proxy pattern can be used to control access to another class.

Using the Proxy pattern for lazy loading

Let’s consider an example of the proxy pattern implementation in lazy loading objects:

// This class is the class whose object we want to lazily load.
public class ExpensiveClass {
  private String expensiveData;

  public ExpensiveClass(String expensiveData) {
    this.expensiveData = expensiveData;
  }

  public String getExpensiveData() {
    return expensiveData;
  }
}

// The proxy class is a class that controls access to the ExpensiveClass class.
public class ExpensiveClassProxy {
  private ExpensiveClass expensiveObject;

  public ExpensiveClassProxy() {
    // Initially, the expensiveObject field is null, because we haven't loaded the
    // expensiveObject object yet.
    this.expensiveObject = null;
  }

  // The proxy class provides a method for accessing the expensiveObject's data.
  // This method lazily loads the expensiveObject object, if it hasn't been loaded yet.
  public String getExpensiveData() {
    if (expensiveObject == null) {
      // If the expensiveObject object hasn't been loaded yet, then load it.
      expensiveObject = new ExpensiveClass("Lazy-loaded expensive data");
    }

    // Return the expensiveObject's data.
    return expensiveObject.getExpensiveData();
  }
}

Pitfalls of the Proxy Pattern

The proxy pattern, while having the benefits mentioned above, presents some possible pitfalls:

  • Introduce additional complexity to a system by creating an extra layer of abstraction. In turn, this can make the system harder to understand and maintain.
  • Can have a negative impact on performance. This can result in slower method calls and increased memory usage.
  • It’s more difficult to unit test the code since it requires the use of mock objects to simulate the behavior of the proxy class.

Conclusion

This post introduces you to the proxy design pattern in Java. While providing benefits by solving real-world problems, developers need to use this pattern with care to avoid the possible pitfalls.

Leave a Comment