JavaRush /Java Blog /Random EN /Proxy design pattern

Proxy design pattern

Published in the Random EN group
In programming, it is important to properly plan the application architecture. An indispensable tool for this is design patterns. Today we’ll talk about Proxy, or in other words, Deputy.

Why do you need a Deputy?

This pattern helps solve problems associated with controlled access to an object. You may have a question: “Why do we need such controlled access?” Let's look at a couple of situations that will help you figure out what's what.

Example 1

Let's imagine that we have a large project with a bunch of old code, where there is a class responsible for downloading reports from the database. The class works synchronously, that is, the entire system is idle while the database processes the request. On average, a report is generated in 30 minutes. Because of this feature, its upload starts at 00:30, and management receives this report in the morning. During the analysis, it turned out that it is necessary to receive the report immediately after it is generated, that is, within a day. It is impossible to reschedule the start time, since the system will wait for a response from the database. The solution is to change the operating principle by starting the upload and report generation in a separate thread. This solution will allow the system to operate as usual, and management will receive fresh reports. However, there is a problem: the current code cannot be rewritten, since its functions are used by other parts of the system. In this case, you can introduce an intermediate proxy class using the Deputy pattern, which will receive a request to upload a report, log the start time and launch a separate thread. When the report is generated, the thread will complete its work and everyone will be happy.

Example 2

The development team creates a poster website. To obtain data about new events, they turn to a third-party service, interaction with which is implemented through a special closed library. During development, a problem arose: a third-party system updates data once a day, and a request to it occurs every time the user refreshes the page. This creates a large number of requests and the service stops responding. The solution is to cache the service response and provide visitors with the saved result on each reboot, updating this cache as needed. In this case, using the Deputy pattern is an excellent solution without changing the finished functionality.

How the pattern works

To implement this pattern, you need to create a proxy class. It implements a service class interface, simulating its behavior for client code. Thus, instead of the real object, the client interacts with its proxy. Typically, all requests are passed on to the service class, but with additional actions before or after its call. Simply put, this proxy object is a layer between the client code and the target object. Let's look at an example of caching a request from a very slow old disk. Let it be an electric train schedule in some ancient application, whose operating principle cannot be changed. The disk with the updated schedule is inserted every day at a fixed time. So we have:
  1. Interface TimetableTrains.
  2. The class TimetableElectricTrainsthat implements this interface.
  3. It is through this class that client code interacts with the disk file system.
  4. Client class DisplayTimetable. Its method printTimetable()uses class methods TimetableElectricTrains.
The scheme is simple: Proxy Design Pattern - 2Currently, every time a method is called, printTimetable()the class TimetableElectricTrainsaccesses the disk, unloads data and provides it to the client. This system works well, but is very slow. Therefore, it was decided to increase system performance by adding a caching mechanism. This can be done using the Proxy pattern: Proxy Design Pattern - 3This way the class DisplayTimetablewill not even notice that it is interacting with the class TimetableElectricTrainsProxyand not with the previous one. The new implementation loads the schedule once a day, and upon repeated requests, returns the already loaded object from memory.

For what tasks is it better to use Proxy?

Here are a few situations in which this pattern will definitely come in handy:
  1. Caching.
  2. Lazy implementation is also known as lazy implementation. Why load an object all at once when you can load it as needed?
  3. Logging requests.
  4. Interim data and access checks.
  5. Launching parallel processing threads.
  6. Recording or counting the history of a call.
There are other use cases as well. Understanding the principle of operation of this pattern, you yourself can find a successful application for it. At first glance, Deputy does the same thing as Facade , but it's not. The Proxy has the same interface as the service object. Also, do not confuse the pattern with Decorator or Adapter . The Decorator provides an extended interface, while the Adapter provides an alternative one.

Advantages and disadvantages

  • + You can control access to the service object as you wish;
  • + Additional capabilities for managing the life cycle of a service object;
  • + Works without a service object;
  • + Improves code performance and security.
  • - There is a risk of deterioration in performance due to additional treatments;
  • - Complicates the structure of program classes.

Substitute pattern in practice

Let's implement a system with you that reads train schedules from disk:
public interface TimetableTrains {
   String[] getTimetable();
   String getTrainDepartureTime();
}
A class that implements the main interface:
public class TimetableElectricTrains implements TimetableTrains {

   @Override
   public String[] getTimetable() {
       ArrayList<String> list = new ArrayList<>();
       try {
           Scanner scanner = new Scanner(new FileReader(new File("/tmp/electric_trains.csv")));
           while (scanner.hasNextLine()) {
               String line = scanner.nextLine();
               list.add(line);
           }
       } catch (IOException e) {
           System.err.println("Error:  " + e);
       }
       return list.toArray(new String[list.size()]);
   }

   @Override
   public String getTrainDepartureTime(String trainId) {
       String[] timetable = getTimetable();
       for(int i = 0; i<timetable.length; i++) {
           if(timetable[i].startsWith(trainId+";")) return timetable[i];
       }
       return "";
   }
}
Every time you try to get the schedule of all trains, the program reads the file from disk. But these are still flowers. The file is also read every time you need to get the schedule for just one train! It's good that such code only exists in bad examples :) Client class:
public class DisplayTimetable {
   private TimetableTrains timetableTrains = new TimetableElectricTrains();

   public void printTimetable() {
       String[] timetable = timetableTrains.getTimetable();
       String[] tmpArr;
       System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
       for(int i = 0; i < timetable.length; i++) {
           tmpArr = timetable[i].split(";");
           System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
       }
   }
}
Example file:

9B-6854;Лондон;Прага;13:43;21:15;07:32
BA-1404;Париж;Грац;14:25;21:25;07:00
9B-8710;Прага;Вена;04:48;08:49;04:01;
9B-8122;Прага;Грац;04:48;08:49;04:01
Let's test:
public static void main(String[] args) {
   DisplayTimetable displayTimetable = new DisplayTimetable();
   displayTimetable.printTimetable();
}
Conclusion:

Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
9B-6854  Лондон  Прага    13:43         21:15         07:32
BA-1404  Париж   Грац   14:25         21:25         07:00
9B-8710  Прага   Вена   04:48         08:49         04:01
9B-8122  Прага   Грац   04:48         08:49         04:01
Now let's go through the steps of implementing our pattern:
  1. Define an interface that allows you to use a new proxy instead of the original object. In our example this is TimetableTrains.

  2. Create a proxy class. It must contain a reference to a service object (create in a class or pass in a constructor);

    Here is our proxy class:

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return timetableTrains.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return timetableTrains.getTrainDepartureTime(trainId);
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    At this stage, we simply create a class with a reference to the original object and pass all calls to it.

  3. We implement the logic of the proxy class. Basically the call is always redirected to the original object.

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           for(int i = 0; i < timetableCache.length; i++) {
               if(timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    The method getTimetable()checks whether the schedule array is cached in memory. If not, it issues a request to load the data from disk, storing the result. If the request is already running, it will quickly return an object from memory.

    Thanks to its simple functionality, the getTrainDepartireTime() method did not have to be redirected to the original object. We simply duplicated its functionality into a new method.

    You can't do that. If you had to duplicate code or perform similar manipulations, it means something went wrong and you need to look at the problem from a different angle. In our simple example there is no other way, but in real projects, most likely, the code will be written more correctly.

  4. Replace the creation of the original object in the client code with a replacement object:

    public class DisplayTimetable {
       // Измененная link
       private TimetableTrains timetableTrains = new TimetableElectricTrainsProxy();
    
       public void printTimetable() {
           String[] timetable = timetableTrains.getTimetable();
           String[] tmpArr;
           System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
           for(int i = 0; i<timetable.length; i++) {
               tmpArr = timetable[i].split(";");
               System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
           }
       }
    }

    Examination

    
    Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
    9B-6854  Лондон  Прага    13:43         21:15         07:32
    BA-1404  Париж   Грац   14:25         21:25         07:00
    9B-8710  Прага   Вена   04:48         08:49         04:01
    9B-8122  Прага   Грац   04:48         08:49         04:01

    Great, it works correctly.

    You can also consider a factory that will create both the original object and a replacement object depending on certain conditions.

Useful link instead of a dot

  1. Excellent article about patterns and a little about “Deputy”

That's all for today! It would be nice to go back to learning and test your new knowledge in practice :)
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION