Implementation

I’m ready — let’s implement it! (source)

For the following example of the Proxy design pattern, we will implement the caching proxy.

In my opinion, in any application which loads resources from an external service, it is quite a common problem to improve the performance and reduce load times of the data. It is possible to optimise and at least partially resolve it by implementing the caching layer.

Let’s say we have a list of customers with some basic information — customer’s id and name. Any additional customer data should be loaded from an external web service. When providing the general list of customers, additional information is not loaded nor used. However, it could be accessed by selecting a specific customer and loading its data from the customer details service. To reduce the number of requests sent to the external service, it makes sense to introduce a caching layer to the application and provide the already loaded information from the cache for future requests.

To achieve this, the Proxy design pattern is a great choice! Let’s check the class diagram first and then implement a proxy for the customer details service.

Class diagram

The class diagram below shows the implementation of the Proxy design pattern:

Class Diagram — Implementation of the Proxy design pattern

Customer class is used to store information about the customer. One of its properties is the CustomerDetails which stores additional data about the customer e.g. its email, hobby and position.

ICustomerDetailsService is an abstract class which is used as an interface for the customer details service:

getCustomerDetails() — an abstract method which returns details for the specific customer.

CustomerDetailsService is the “real” customer details service which implements the abstract class ICustomerDetailsService and its methods.

CustomerDetailsServiceProxy is a proxy service which contains the cache (dictionary object) and sends the request to the real CustomerDetailsService only if the customer details object is not available in the cache.

ProxyExample initialises and contains the proxy object of the real customer details service. When a user selects the option to see more details about the customer, the dialog window appears and loads details about the customer. If the details object is already stored inside the cache, the proxy service returns that object instantly. Otherwise, a request is sent to the real customer details service and the details object is returned from there.

Customer

A simple class to store information about the customer: its id, name and details. Also, the constructor generates random id and name values when initialising the Customer object.

CustomerDetails

A simple class to store information about customer details: id to map the details information with the corresponding customer, e-mail address, hobby and the current position (job title).

ICustomerDetailsService

An interface which defines the getCustomerDetails() method to be implemented by the customer details service and its proxy. Dart language does not support the interface as a class type, so we define an interface by creating an abstract class and providing a method header (name, return type, parameters) without the default implementation.

CustomerDetailsService

A specific implementation of the ICustomerDetailsService interface — the real customer details service. The getCustomerDetails() method mocks the real behaviour of the service and generates random values of customer details.

CustomerDetailsServiceProxy

A specific implementation of the ICustomerDetailsService interface — a proxy for the real customer details service. Before making a call to the customer details service, the proxy service checks whether the customer details are already fetched and saved in the cache. If yes, the customer details object is returned from the cache, otherwise, a request is sent to the real customer service and its value is saved to the cache and returned.

Example

First of all, a markdown file is prepared and provided as a pattern’s description:

ProxyExample contains the proxy object of the real customer details service. When the user wants to see customer details, the showDialog() method is triggered (via the showCustomerDetails() method) which opens the dialog window of type CustomerDetailsDialog and passes the proxy object via its constructor as well as the selected customer’s information — the Customer object.

The CustomerDetailsDialog class uses the passed proxy service on its state’s initialisation, hence loading details of the selected customer.

The CustomerDetailsDialog class does not care about the specific type of customer details service as long as it implements the ICustomerDetailsService interface. As a result, an additional caching layer could be used by sending the request through the proxy service, hence improving the general performance of the application, possibly saving some additional network data and reducing the number of requests sent to the real customer details service as well. Also, if you want to call the real customer details service directly, you can just simply pass it via the CustomerDetailsDialog constructor — no additional changes are needed in the UI code since both the real service and its proxy implements the same interface.

As you can see in the example, when trying to load the specific customer’s details for the first time, it takes some time for the information to load from the service. However, when the same information is accessed once again, it is provided from the cache stored in the proxy service, hence the request is not sent to the real customer details service — the customer details information is provided instantly.

All of the code changes for the Proxy design pattern and its example implementation could be found here.