Real-Time Examples of Smart Reference Proxy Design Pattern in C#

Real-Time Examples of Smart Reference Proxy Design Pattern in C#

In this article, I will discuss Real-Time Examples of Smart Reference Proxy Design Patterns in C#. Please read our previous article, discussing Real-Time Examples of the Firewall Proxy Design Pattern in C#. At the end of this article, you will understand the following pointers.

  1. What is the Smart Reference Proxy Design Pattern in C#?
  2. Multiple Real-Time Examples of Smart Reference Proxy Design Pattern in C#
  3. Advantages and Disadvantages of Virtual Proxy Design Pattern in C#
  4. When to use Smart Reference Proxy Design Pattern in C#?
Smart Reference Proxy Design Pattern in C#

The Smart Reference Proxy design pattern is a variation of the Proxy design pattern. While the Proxy pattern generally controls access to an object, the Smart Reference Proxy pattern adds functionality to objects, such as reference counting, logging, access control, etc., without modifying their actual classes.

Structure

In a basic implementation of the Smart Reference Proxy design pattern in C#, you’d typically have:

  • Subject: An interface defining the operations for the RealSubject and Proxy.
  • RealSubject: The class providing functionality that the proxy will manage/control.
  • Proxy: The class managing and controlling access or functionality for the RealSubject.
Example to Understand Smart Reference Proxy Design Pattern in C#

Below is a simplified implementation of the Smart Reference Proxy pattern in C#:

using System;
namespace SmartReferenceProxyDesignPattern
{
    //Subject(IService.cs)
    public interface IService
    {
        void PerformOperation();
    }

    //RealSubject(Service.cs)
    public class Service : IService
    {
        public void PerformOperation()
        {
            Console.WriteLine("Service is performing an operation.");
        }
    }

    //Proxy(ServiceProxy.cs)
    public class ServiceProxy : IService
    {
        private Service _service;
        private int _referenceCount;

        public ServiceProxy()
        {
            _service = new Service();
            _referenceCount = 0;
        }

        public void PerformOperation()
        {
            LogAccess();
            IncreaseReferenceCount();

            _service.PerformOperation();

            DecreaseReferenceCount();
        }

        private void LogAccess()
        {
            Console.WriteLine("ServiceProxy: Accessing service...");
        }

        private void IncreaseReferenceCount()
        {
            _referenceCount++;
            Console.WriteLine($"ServiceProxy: Increasing reference count to {_referenceCount}.");
        }

        private void DecreaseReferenceCount()
        {
            _referenceCount--;
            Console.WriteLine($"ServiceProxy: Decreasing reference count to {_referenceCount}.");
        }
    }
    //Client
    public class Client
    {
        public void ClientCode(IService service)
        {
            service.PerformOperation();
        }
    }

    //Client Code
    //Testing Smart Reference Proxy Design Pattern
    public class Program
    {
        public static void Main()
        {
            IService serviceProxy = new ServiceProxy();
            Client client = new Client();

            client.ClientCode(serviceProxy);
            Console.ReadKey();
        }
    }
}
Explanation
  • Service performs the actual operations, oblivious that a proxy provides additional functionality.
  • ServiceProxy controls access to Service, adding extra functionality like logging and reference counting.
  • The client interacts with IService (either Service or ServiceProxy) via the PerformOperation method.

In the example above, the ServiceProxy is the Smart Reference, providing additional functionality such as logging and reference counting without modifying the Service class.

Real-Time Examples of Smart Reference Proxy Design Pattern in C#

Real-Time Example of Smart Reference Proxy Design Pattern in C#

Consider a real-time scenario using the Smart Reference Proxy pattern: Database Connection Pooling. In modern applications, database connections are expensive to create and destroy frequently. Hence, a connection pool is maintained where connections are reused. When an application needs a database connection, it requests one from the pool rather than creating a new one. Once the application is done with the connection, it returns it to the pool, allowing another part of the application (or another application altogether) to be reused.

Here, the Smart Reference Proxy pattern can be employed to manage these pooled connections. The proxy can be responsible for:

  • Returning an available connection from the pool.
  • Automatically closing and returning the connection to the pool after a timeout or after the operation.
  • Logging when a connection is taken and returned.
  • Managing reference counts if needed.
Example Code:
using System;
using System.Collections.Generic;
using System.Linq;
namespace SmartReferenceProxyDesignPattern
{
    //IDatabaseConnection (Subject)
    public interface IDatabaseConnection
    {
        void ExecuteQuery(string query);
    }

    //DatabaseConnection (RealSubject)
    public class DatabaseConnection : IDatabaseConnection
    {
        public void ExecuteQuery(string query)
        {
            Console.WriteLine($"Executing query: {query}");
        }
    }

    //DatabaseConnectionProxy (Proxy)
    public class DatabaseConnectionProxy : IDatabaseConnection
    {
        private DatabaseConnection _connection;
        private static List<DatabaseConnection> _availableConnections = new List<DatabaseConnection>
        {
            new DatabaseConnection(), new DatabaseConnection(), new DatabaseConnection()
        };

        public DatabaseConnectionProxy()
        {
            _connection = GetConnectionFromPool();
        }

        public void ExecuteQuery(string query)
        {
            Console.WriteLine("Proxy: Checking connection health...");
            // Assume there's a method to check connection health, omitted for brevity.
            if (ConnectionIsHealthy())
            {
                _connection.ExecuteQuery(query);
                ReturnConnectionToPool(_connection);
            }
            else
            {
                Console.WriteLine("Proxy: Connection is unhealthy. Trying another one...");
                _connection = GetConnectionFromPool();
                _connection.ExecuteQuery(query);
                ReturnConnectionToPool(_connection);
            }
        }

        private DatabaseConnection GetConnectionFromPool()
        {
            // For simplicity, just get the first available connection.
            var connection = _availableConnections.FirstOrDefault();
            _availableConnections.Remove(connection);
            return connection;
        }

        private void ReturnConnectionToPool(DatabaseConnection connection)
        {
            _availableConnections.Add(connection);
        }

        private bool ConnectionIsHealthy()
        {
            // Logic to check connection health, omitted for brevity.
            return true;
        }
    }

    //Client Code
    //Testing Smart Reference Proxy Design Pattern
    public class Program
    {
        public static void Main()
        {
            IDatabaseConnection connection = new DatabaseConnectionProxy();
            connection.ExecuteQuery("SELECT * FROM users");
            Console.ReadKey();
        }
    }
}

In this example, the DatabaseConnectionProxy is the Smart Reference Proxy for the real database connection (DatabaseConnection). It manages the connection pool, ensuring that connections are healthy before use and returning them to the pool once done.

Real-Time Example of Smart Reference Proxy Design Pattern in C#

Another real-time example is Image Proxy for a Photo Viewer application. Suppose you are developing a photo viewer application that displays high-resolution images from a remote server. Loading all high-res images simultaneously can be resource-intensive and slow, especially with limited bandwidth. Instead, the application can load a lightweight placeholder image immediately and then lazily fetch the high-res image in the background.

In this case, the Smart Reference Proxy pattern can represent these images. The proxy can:

  • Initially, display a lightweight placeholder image.
  • Load the high-res image in the background.
  • Swap the placeholder with the high-res image once it’s fully loaded.
  • Cache the high-res image for future requests.
Example Code:
using System;
using System.Threading.Tasks;

namespace SmartReferenceProxyDesignPattern
{
    //IImage (Subject)
    public interface IImage
    {
        void Display();
    }

    //HighResolutionImage (RealSubject)
    public class HighResolutionImage : IImage
    {
        private string _filePath;

        public HighResolutionImage(string filePath)
        {
            _filePath = filePath;
            LoadImageFromDisk(); // Simulating loading the image from a remote server or disk.
        }

        private void LoadImageFromDisk()
        {
            Console.WriteLine($"Loading HighResolutionImage from: {_filePath}");
            // Logic to load the image (omitted for brevity).
        }

        public void Display()
        {
            Console.WriteLine($"Displaying HighResolutionImage from: {_filePath}");
        }
    }

    //ImageProxy (Proxy)
    public class ImageProxy : IImage
    {
        private string _filePath;
        private HighResolutionImage _highResImage;

        public ImageProxy(string filePath)
        {
            _filePath = filePath;
        }

        public void Display()
        {
            if (_highResImage == null)
            {
                Console.WriteLine("Displaying Placeholder Image.");
                LoadHighResolutionImageInBackground();
            }
            else
            {
                _highResImage.Display();
            }
        }

        private void LoadHighResolutionImageInBackground()
        {
            // Simulate background loading.
            Task.Run(() =>
            {
                _highResImage = new HighResolutionImage(_filePath);
                // Once loaded, you could notify the UI to refresh and display the actual image.
            });
        }
    }
    
    //Client Code
    //Testing Smart Reference Proxy Design Pattern
    public class Program
    {
        public static void Main()
        {
            IImage image = new ImageProxy("path_to_remote_image.jpg");
            image.Display(); // Initially displays the placeholder.
                             // After some delay, the high-res image will be loaded and displayed.
            Console.ReadKey();
        }
    }
}

In this example, ImageProxy serves as the Smart Reference Proxy for HighResolutionImage. When a user tries to view an image, the proxy immediately displays a placeholder while fetching the high-res image in the background. Once the high-res image is loaded, the proxy ensures that subsequent requests are served directly from the loaded image, effectively caching it.

When to use Smart Reference Proxy Design Pattern in C#?

The Smart Reference Proxy Design Pattern is useful when you want to add some responsibilities or behaviors to individual objects without affecting other objects of the same class or modifying the object’s actual code. Specifically, in C# or any other object-oriented language, you might consider using the Smart Reference Proxy pattern in the following scenarios:

  • Lazy Initialization: When you want to defer the creation and initialization of a resource-heavy object until it’s actually needed. The proxy can manage the lazy instantiation and give clients the illusion that the object has been initialized, but the actual instantiation happens only upon a real request.
  • Access Control: If you need to control access to the real object based on certain criteria (like permissions, roles, or states), a proxy can act as a gatekeeper, deciding when to allow access to the object.
  • Logging and Monitoring: When you want to log the actions performed on an object or monitor its usage for debugging, performance analysis, or auditing purposes.
  • Caching: If you have expensive operations regarding time or resources and yield results that can be reused for subsequent calls with the same parameters, a proxy can cache these results and serve them directly, improving performance.
  • Reference Counting: In scenarios where you need to keep track of the number of references to an object and delete the object when no references are left. This is more relevant in languages with manual memory management, but there are situations in C# (like COM interop) where this can be useful.
  • Remote Object Access: If you’re accessing an object in a different address space (perhaps on a different server), the proxy can hide the intricacies of remote communication, making the remote object appear local.
  • Virtual Proxy: When you have objects that need significant resources, but not all parts of the object are always needed. The proxy can initially load a small, lightweight representation and defer the full loading until required.
  • Protection Proxy: When you want to protect the real object from unwanted or harmful interactions. This slightly differs from access control as it deals more with ensuring the object’s integrity.
  • Load Balancing: If requests to an object should be distributed across replicas or instances to balance the load. The proxy can act as a load balancer, forwarding requests to an appropriate instance.
When not to use Smart Reference Proxy Design Pattern in C#?

While there are many scenarios where the Smart Reference Proxy pattern can be beneficial, it’s not always the right choice. Avoid using it when:

  • The overhead of introducing proxies outweighs the benefits.
  • The problem can be more elegantly solved using other patterns or techniques.
  • You’re only adding unnecessary complexity without clear advantages.
Advantages and Disadvantages of Smart Reference Proxy Design Pattern in C#

The Smart Reference Proxy design pattern can offer both benefits and potential downsides. Here’s a breakdown:

Advantages of Smart Reference Proxy Design Pattern in C#:
  • Separation of Concerns: The pattern allows you to separate the core functionality of an object from aspects that may not be its primary concern, such as logging, caching, or access control. This ensures that each class adheres to the Single Responsibility Principle.
  • Open/Closed Principle: The Proxy pattern aligns with the Open/Closed Principle since you can introduce new proxies without changing the underlying or client code.
  • Flexibility: With a proxy in place, it’s easier to add, modify, or remove the ancillary behaviors without touching the main object’s code. For instance, turning off logging or switching from eager to lazy loading can be as simple as tweaking the proxy or swapping it out.
  • Protection: By using a proxy, you can restrict direct access to the object, providing protection and control over the object’s life cycle and accessibility.
  • Transparency: When done correctly, proxies can be transparent to the client, meaning the client doesn’t need to be aware of the proxy’s presence. This ensures that the client interacts with the proxy and the real object.
Disadvantages of Smart Reference Proxy Design Pattern in C#:
  • Complexity: Introducing proxies can increase the overall complexity of the codebase. You might have a corresponding proxy for every class, which can be cumbersome to manage.
  • Performance Overhead: Every request through a proxy involves an additional layer of indirection. While this overhead is typically minor, it might be a concern in high-performance scenarios or when many nested proxies are used.
  • Maintenance: As you introduce more proxies, you’ll need to ensure they’re updated in line with changes to their corresponding real objects, which can increase maintenance efforts.
  • Potential Misuse: Sometimes, proxies can be overused for purposes better handled by other patterns or mechanisms. For instance, using a proxy just for logging might be overkill when Aspect-Oriented Programming (AOP) could be a more appropriate solution.
  • Debugging: Debugging can become slightly more tricky, especially if there are multiple proxies or a mix of proxies and decorators. Stepping through code can take you through many layers of proxy code before reaching the actual object.

While the Smart Reference Proxy pattern offers a robust means of controlling and extending access to objects, evaluating its appropriateness is essential based on the problem at hand. Over-reliance or misuse can lead to more complications than solutions.

In the next article, I will discuss the Behavioral Design Pattern in C# with Examples. Here, in this article, I try to explain the Real-Time Examples of Smart Reference Proxy Design Patterns in C#. I hope you enjoy this Smart Reference Proxy Design Pattern in Real-time Examples using the C# article.

Leave a Reply

Your email address will not be published. Required fields are marked *