Menu Close

Object Oriented Design Pattern: Proxy

proxy

Explanation

The Proxy design pattern uses a proxy object as stand-in for the original object. For instance, instead of making a (resource intensive) call directly, the proxy class holds a reference to this call, so it is able to make to call at the right moment. Additionally, you can add additional functionality before and/or after the call. So a proxy is a class in between the calling and the called class.

A few examples of practical implementations could be the delay of loading images, so that an image will only be loaded as it becomes visible to the user or maybe the adding of logging and/or caching when accessing the resource intensive object.

Use cases

  • Timing the calls to resource intensive sources
  • Adding extra functionality alongside the call to the underlying object of the proxy

    Pitfalls

    • The extra layer can cause unexpected behavior, bottlenecks or performance overhead
    • Increased complexity

    Proxy variants

    According to Gamma et al. (1994), the proxy pattern is applicable in several ways:

    • Remote proxy: local representative for (accessing) a remote object.
    • Virtual proxy: only uses the expensive objects it references when needed.
    • Protection proxy: controls access to an object in the context of rights management.
    • Smart reference: adds functionality to the call to the underlying object, like counting.

    Example C#

    This example shows a Winget download proxy class. This class will only download winget if it does not detect it on your local hard-drive to the default location the application downloads to. This logic is visible in the GetWinget method of the WingetDownloadProxy class.

    using System.Diagnostics;
    using System.IO;
    using System.Net.Http;
    
    namespace DesignPatterns.Proxy
    {
        /// <summary>
        /// Downloads the winget application when it does not find the file locally
        /// </summary>
        internal class WingetDownloadProxy
        {
            HttpClient httpClient = new HttpClient();
            string DownloadDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            
            /// <summary>
            /// Download winget only if the file is not present locally
            /// </summary>
            /// <returns></returns>
            public async Task GetWinget()
            {
                try
                {
                    string? fileName = await GetFileName();
                    if (null != fileName)
                    {
                        // check local presence of file
                        String filePath = Path.Combine(DownloadDirectory, fileName);
                        if (!File.Exists(filePath))
                        {
                            Debug.WriteLine($"Trying to download winget to {DownloadDirectory}");
                            await DownloadWinget();
                        }
                        else
                        {
                            Debug.WriteLine($"Winget has already been downloaded: {filePath}");
                        }
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }
    
            /// <summary>
            /// Returns filename from http header
            /// </summary>
            /// <returns></returns>
            private async Task<string?> GetFileName()
            {
                try
                {
                    using (HttpResponseMessage response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://aka.ms/getwinget")))
                    {
                        response.EnsureSuccessStatusCode();
                        if (response.Content.Headers.ContentDisposition != null)
                        {
                            return response.Content.Headers.ContentDisposition.FileName;
                        }
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }
                return null;
            }
    
            /// <summary>
            /// Downloads winget to default directory
            /// </summary>
            /// <returns></returns>
            private async Task DownloadWinget()
            {
                try
                {
                    using (HttpResponseMessage response = await httpClient.GetAsync("https://aka.ms/getwinget"))
                    {
                        response.EnsureSuccessStatusCode();
                        byte[] fileBytes = await response.Content.ReadAsByteArrayAsync();
                        string? fileName = response.Content.Headers.ContentDisposition?.FileName ?? "winget";
                        await File.WriteAllBytesAsync(Path.Combine(DownloadDirectory, fileName), fileBytes);
                    };
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }
        }
    }
    
    C#

    And for the Program (main) class:

    using DesignPatterns.Proxy;
    class Program
    {
        public static async Task Main(string[] args)
        {
            WingetDownloadProxy proxy = new WingetDownloadProxy();
            await proxy.GetWinget();
        }
    }
    C#

    Conclusion

    Instead of using the HttpClient class directly, the program uses the WingetDownloadProxy class which wraps around it and only downloads winget when it does not find its filename on the local hard-drive.

    Proxy design pattern class diagram

    References

    Freeman, E., Bates, B., & Sierra, K. (2004). Head first design patterns.

    Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software.

    Wikipedia contributors. (2024, 10 september). Software design pattern. Wikipedia. https://en.wikipedia.org/wiki/Software_design_pattern

    Related Posts