This is the second post in the Structural Design Patterns series and in this blog post, we will discuss the Proxy Design Pattern and its implementation using the C# programming language.
The Proxy Design Pattern provides an intermediate or a placeholder object for another object that servers the functionality. The Proxy design pattern falls into the Structural Pattern category as defined by the Gang of Four (GoF). If you are new to design patterns, I would highly recommend you to read the introductory post “Introduction to Design Patterns“
What is Proxy Design Pattern?
A Proxy Design Pattern provides a way to represent a different object and allows to access its functionality through it. Here the object representing the functionality is called the Proxy object and the object whose functionality is being represented via Proxy Object is called the “Real Subject” or “Real Object”.
The pattern adds a level of indirection by accepting requests from the client and passing the request to the Real Subject. (The request from the client can be modified by the Proxy class based on a need basis before passing it to the Real Subject).
As defined by Gang of Four, the “Proxy Design Pattern provides a surrogate or placeholder for another object to control access to it“.
In a simple term, A Proxy Design Pattern allows us to create a class that represents the functionality of other classes.
When to consider Proxy Design Pattern
The Proxy Pattern can be used in scenarios like, when data can be cached to improve responsiveness, the Real Subject is in a remote location and requires wrapping and unwrapping of requests and making remote calls. The Real Subject should be protected from unauthorized access or they are expensive to create. A few of the different types of Proxy Pattern implementation are discussed below:
Cache Proxy
The Cache Proxy improves the responsiveness by caching relevant data (that does not change frequently or is expensive to create) at the Proxy object level, when a client sends a request, the Proxy checks if the requested data is present with it if the data is found, it is returned to the client without sending the request to the Real Object.
If the data is not present the request is forwarded to the Real Subject the response is returned to the client as well as saved into the Cache Proxy, so that next time it can be served directly without a need to invoke Real object operation thus minimizing request forwarding and improving responsiveness.
Remote Proxy
The Remote Proxy provides a proxy object at a local level which represents an object present at another location. The proxy provides all the functionality of marshaling and unmarshalling the client’s request, invoking the call to Real Subject via a network connection, and returning back the response to the client.
The Remote Proxy manages everything and the client need not worry about how to connect to the Real Subject. A simple example of such a scenario would be a desktop application calling a Web Service.
Protection Proxy
As its name suggests, the Protection Proxy is used to provide some security for Real Subject at the proxy level. If there is a requirement that the access to the Real Subject should only be provided to an authorized user, then the Proxy can provide some mechanism for users to valid themselves first before accessing the Real Object. If the user directly accesses the Real Subject without authorizing himself then access to the Real Object is denied.
Virtual Proxy
Virtual Proxy is used in a scenario when the Real Object is a complex object or expensive to create. The instantiation of the Real Object is deferred until it is required, just like lazy loading.
Let us understand with an example, suppose there is a list representing employees, displaying only its id, first-name, last-name, and department (Virtual Proxy for Employee).
The details of an employee are only displayed when the user explicitly checks for his details (Real Subject) and his details contain data like, salary structure details, leave details, work details, etc. In this scenario a Virtual Proxy can be used to provide basic information and the Real Object is created only when the employee details are explicitly required.
Proxy Pattern – UML
The major classes participating in the UML are as follows:
- ISubject: This is the interface that the RealSubject and the Proxy class will implement. This interface exposes the methods that will be serviced to the client.
- RealSubject: This is the class that implements the actual (real) operations, and the same is used by the Proxy class as a reference to serving the proxy operations.
- Proxy: This is the class that creates, maintains the reference and lifetime of the RealSubject. The class is free to add its own additional implementation before making the call to the RealSubject service.
- Client: This is the client that wants to consume the RealSubject service.
C# Implementation
The Proxy Design Pattern can be implemented in various ways based on the requirement as we have seen earlier. Let us build up a simple console application to implement Cache Proxy. The requirement of the application is given below:
Requirement description
Let us assume we have a Remote Database for which we need to provide the Cache Proxy implementation so that the roundtrip to the database can be reduced and we can improve the performance of the client application. The requirement of the application is as follows:
- A Remote database exists and stores the key-value pair in a Dictionary. The value can be retrieved using the GetValue and a new value can be added using the SetValue methods.
- A Cache Proxy pattern should be implemented, to serve the client, with the following requirements.
- The proxy should store the key-value pair locally whenever it fetches it from the remote database.
- The proxy should first check if the key-value pair exists locally, if the key is found it should be returned to the client. If the key is not found locally then it should be fetched from the remote database, store it locally first and then return it to the client.
The artifacts of the implementation are as follows:
- ICache.cs: This is the interface that is exposed to the client and represents the ISubject interface in the pattern implementation.
- RemoteDatabase.cs: This class represents the remote database class and the Real Subject as per Proxy Design Pattern. The class implements the ICache interface.
- CacheProxy.cs: This class provides the implementation of the Proxy pattern, and implements all the caching requirements. The class also implements the ICache interface.
- Client.cs: This is the client application, that uses the ICache interface to invoke the services offered by the remote database, but instead these functions will be offered by the CacheProxy in real-time.
Requirement implementation in C#
ICache.cs: This is the ICache interface that exposes two methods as per requirement and will be implemented by both the RemoteDatabase and CacheProxy classes.
/// <summary> /// Represent the ISubject interface of the Proxy Pattern /// </summary> public interface ICache { /// <summary> /// Represents the GetValue service of the remote database /// </summary> /// <param name="key"></param> /// <returns></returns> string GetValue(string key); /// <summary> /// Represents the SetValue service of the remote database /// </summary> /// <param name="key"></param> /// <param name="value"></param> void SetValue(string key, string value); }
RemoteDatabase.cs: This class has a Dictionary to store the value and implements the ICache.cs interface.
/// <summary> /// Represents an RealSubject of the Proxy Design Pattern /// Assumption this is a class that exist remotely, and the invokcation require roundtrip to the server /// </summary> public class RemoteDatabase : ICache { //Represents database objects private Dictionary<string, string> Configuration; public RemoteDatabase() { Configuration = new Dictionary<string, string>(); SetConfiguration(); } public string GetValue(string key) { if (Configuration.ContainsKey(key)) { Console.WriteLine($"RemoteDatabase<GetValue>: [FETCH] Key: {key}, Value: {Configuration[key]}"); return Configuration[key]; } else return string.Empty; } public void SetValue(string key, string value) { if (Configuration.ContainsKey(key)) { Console.WriteLine($"RemoteDatabase<SetValue> [UPDATED]: Key: {key}, Value: {Configuration[key]}"); Configuration[key] = value; } else { Configuration.Add(key, value); Console.WriteLine($"RemoteDatabase<SetValue> [ADDED]: Key: {key}, Value: {Configuration[key]}"); } } private void SetConfiguration() { Configuration.Add("SessionId", Guid.NewGuid().ToString()); Configuration.Add("FirstName", "John"); }
CacheProxy.cs: This is the proxy implementation class as per requirement. Here in this class whenever a value is fetched from the RemoteDatabase class, it is also updated locally in its private dictionary, the same case is also implemented for whenever the value is updated in the RemoteDatabase class.
/// <summary> /// Represent the Proxy class of the Proxy Design Pattern /// </summary> public class CacheProxy : ICache { private Dictionary<string, string> CachedConfiguration; private RemoteDatabase databaseCache; public CacheProxy() { CachedConfiguration = new Dictionary<string, string>(); databaseCache = new RemoteDatabase(); } public string GetValue(string key) { if (CachedConfiguration.ContainsKey(key)) { Console.WriteLine($"CacheProxy<GetValue>: [PROXY-FETCH] Key: {key}, Value: {CachedConfiguration[key]}"); return CachedConfiguration[key]; } else { var value = databaseCache.GetValue(key); if (!string.IsNullOrEmpty(value)) { CachedConfiguration.Add(key, value); Console.WriteLine($"CacheProxy<GetValue>: [PROXY-UPDATED] Key: {key}, Value: {CachedConfiguration[key]}"); return value; } else Console.WriteLine($"Invalid key: {key}"); } return string.Empty; } public void SetValue(string key, string value) { databaseCache.SetValue(key, value); CachedConfiguration[key] = value; Console.WriteLine($"CacheProxy<SetValue>: [PROXY-UPDATED] Key: {key}, Value: {CachedConfiguration[key]}"); } }
Client.cs: This is the client application, that uses the ICache interface to use the RemoteDatabase service, which is served via the CacheProxy class.
class Client { static void Main(string[] args) { ICache cache = new CacheProxy(); Console.WriteLine($"Client: {cache.GetValue("SessionId")}"); Console.WriteLine(); Console.WriteLine($"Client: {cache.GetValue("SessionId")}"); Console.WriteLine(); cache.SetValue("UserName", "John"); Console.WriteLine($"Client: {cache.GetValue("UserName")}"); Console.WriteLine(); cache.SetValue("UserName", "Doe"); Console.WriteLine($"Client: {cache.GetValue("UserName")}"); Console.WriteLine(); } }
Application Output:
Advantages of Proxy Design Pattern
A few of the advantages of the Proxy Pattern are as follows:
- The Virtual Proxy pattern helps us to defer the creation of Real Subject until it is fully required.
- The Remote Proxy helps to do the heavy lifting of wrapping and unwrapping, making the remote calls to the real object for the client. The client does not know where the Real Subject resides.
- Cache Proxy help to improve the application performance by providing the frequently accessed resources.
- Protection Proxy allows us to add an additional security layer to avoid unauthorized access to the resources.
- The implementation of the Real Subject can be changed dynamically as it derives from the ISubject interface.
Disadvantages of Proxy Design Pattern
A few of the disadvantages of the Proxy Design Pattern are as follows:
- The Proxy Design Pattern introduces an additional layer between the client and the Real Subject, this contributes to the code complexity.
- Additional request forwarding is introduced between the client and the Real Subject.
Conclusion
In this blog post, we try to learn about the Proxy Design Pattern and a few of its different types. The main crux of the pattern is to provide a client-facing class and hide the real class behind it and thus allowing the implementor with a few additional options like validation, caching, filtering, etc to push in before calling the Real Subject.
I hope you found this post helpful. Thanks for visiting. Cheers!!!
[Further Readings: SQLite Studio to manage SQLite databases | Adapter Design Pattern in C# | How to use Blazor EditForm for Model Validation | How to add a new profile in Windows Terminal | How to easily Customize Global Settings in Windows Terminal | How to add Git Bash to Windows Terminal Application | How to customize Windows Terminal Application | How to customize Windows Terminal Key Bindings | How to Install Windows Terminal in Windows 10 | Important Debugging Shortcuts of Visual Studio 2019 | How to publish a Blazor Server Application to IIS | Top 7 Visual Studio 2019 extensions for Web Projects ]