There are practical reasons to want to keep a client from having access to all the functions in an api; consider a bank that has a function in their system called bank.giveMoney(account, amount). Obviously, they wouldn't want this exposed to the world, because then anyone could easily gift themselves an early retirement! Instead, they could "mask" the function so that another class (that doesn't implement the method) has to make a call to it after doing some work of its own.

In software development, the proxy pattern is a simple solution to this kind of problem!

Assumptions

What is the Proxy Pattern?

<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/ProxyPattern_01.png" alt="proxy pattern UML diagram" style="max-width:95%;"> </div>

A proxy is a placeholder class that is able to limit the accessibility of the "real" class. This is useful for cases such as:

  • Security: The proxy allows an opportunity to add authorization to otherwise freely executable functions. Additionally, it will allow an opportunity to hide any sensitive methods from the client such as bankAccount.giveMoney(...)
  • Resource Intensive Objects: Instantiation can be deferred until it is needed, meaning it can "lazy load" real objects only when the proxy method is executed.
  • Remote Access: A proxy can act as a network connection as well, masking the complex implementation underneath the proxy method.

> Proxy is also sometimes referred to as a "wrapper". This can be confusing sometimes as the same term is also given to the <a href="https://www.devmaking.com/learn/design-patterns/adapter-pattern/" target="_blank" style="color:inherit;">Adapter Pattern</a> and <a href="https://www.devmaking.com/learn/design-patterns/decorator-pattern/" target="_blank" style="color:inherit;">Decorator Pattern</a>.

Conceptualization

Imagine that you run a website for a code repository database. The files are publicly viewable, but to promote user activity, you want clients to have an account in order to download any code files.

> Speaking of code, all of the <a href="https://github.com/AndrewMcShane/DevMakingSource" target="_blank" style="color:inherit;">example code</a> for DevMaking is available on Github!

You already have a file grabbing system in place that fetches content based on a url provided:

// Common interface that our real and proxy will implement:
interface IFileGrabber {
   String getFile(String url);
 }

// Our real class:
class RealFileGrabber implements IFileGrabber {
   // Some arbitrary http service to get web-content:
   HttpService http;
   
   String getFile(String url) {
     return http.get(url);
   }
 }

Let's say that we don't want to edit the getFile() method because its used in other places of the website that don't require an account to access. To amend this, we'll create a new class that implements the same interface and call it FileGrabberProxy:

// Our proxy class:
class FileGrabberProxy implements IFileGrabber {
   // 1. 
   IFileGrabber realFileGrabber;
   boolean isRegisteredUser;
   
   FileGrabberProxy(boolean isRegisteredUser) {
     this.isRegisteredUser = isRegisteredUser;
     this.realFileGrabber = new RealFileGrabber();
   }
   // 2.
   String getFile(String url) {
     if(isRegisteredUser) {
       return realFileGrabber.getFile();
     } 
     else {
       return "401 Unauthorized: You must have an account to get this file!";
     } 
   }
 }

Let's dive into the code:

  1. We keep a reference to a "real" object that the proxy is serving as a body double for. This allows us to ensure that after we've done what we need to, we can "forward" the function call from our proxy to the realFileGrabber.
  2. Since we want to implement a user authorization, we pass a boolean into the class on construction that verifies whether or not this is true. When calling getFile(), we check if the user is valid, and then let the realFileGrabber continue with getFile().

Lastly, let's test out how our proxy class works in a client facing scenario. Since implementing an authorization system is out of the scope of this example, we'll use this list of usernames as our "authorization":

// A static "database" for example purposes:
static String[] USERS = {"sports120", "UsernameTaken", "jjohnson"};

Client Code:

static void main(String[] args) {
   // Arguments passed to the file:
   String username = args[0];
   String url = args[1];
   // Checks if the user is in our (poorly made) authorization system: 
   boolean isRegisteredUser = false;
   if(username in USERS) {
     isRegisteredUser = true;
   }
   // Attempt to grab a file using the proxy class:
   IFileGrabber proxy = new FileGrabberProxy(isRegisteredUser);
   print(proxy.getFile(url));
   // Done!
 }

Creating a proxy helps us extend our code without needing to open up files and potentially make breaking mistakes. For instance, if we wanted to create a paid account option, we'd only need to make a new proxy implementation for parts of the website that need it!