Proxy Pattern | Structural Design Pattern
Proxy Pattern
The Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. This can be useful for various purposes, such as controlling access, logging, or implementing lazy loading.
Lazy Loading: One of the primary benefits is lazy loading, allowing the creation or loading of the real object to be deferred until it is actually needed.
Access Control: Proxies can control access to the real object by performing additional checks or actions before allowing clients to interact with it. This is useful for enforcing security measures or other access restrictions.
Real Object: The "real object" is the actual object that the proxy represents. It is created or loaded only when necessary.
Separation of Concerns: The use of a proxy promotes a separation of concerns. The proxy handles details like lazy loading or access control, while the client code focuses on using the object without worrying about these details.
Examples: Common examples include virtual proxies for resource-intensive objects (e.g., loading images) and security proxies for controlling access to sensitive operations or data (e.g., bank account operations).
Example in C#:
A real-world use case for the Proxy Pattern could be implementing a virtual proxy for loading images in a graphical application. Imagine an application that displays high-resolution images, but loading these images might take time. To improve the user experience, you could use a proxy to load the images only when they are actually needed.
using System;
// Subject interface
public interface IImage
{
void Display();
}
// RealImage class representing a high-resolution image
public class RealImage : IImage
{
private string filename;
public RealImage(string filename)
{
this.filename = filename;
LoadImage();
}
private void LoadImage()
{
Console.WriteLine($"Loading image from file: {filename}");
// Simulate loading a high-resolution image
}
public void Display()
{
Console.WriteLine($"Displaying image: {filename}");
}
}
// ProxyImage class representing a proxy for loading images
public class ProxyImage : IImage
{
private RealImage realImage;
private string filename;
public ProxyImage(string filename)
{
this.filename = filename;
}
public void Display()
{
if (realImage == null)
{
realImage = new RealImage(filename);
}
realImage.Display();
}
}
class Program
{
static void Main()
{
// Client code uses ProxyImage to display images
IImage image1 = new ProxyImage("image1.jpg");
IImage image2 = new ProxyImage("image2.jpg");
// Images are loaded only when Display is called
image1.Display();
image2.Display();
}
}
In this example: IImage is the interface shared by RealImage (the real object) and ProxyImage (the surrogate).RealImage represents the actual high-resolution image and includes the logic for loading it.ProxyImage acts as a proxy for RealImage and loads the real image only when Display is called.When you run this program, it will output:
Loading image from file: image1.jpg
Displaying image: image1.jpg
Loading image from file: image2.jpg
Displaying image: image2.jpg
This illustrates how the Proxy Pattern can be applied to defer the creation or loading of resource-intensive objects until they are actually needed.
the proxy allows you to defer the creation or loading of the real object until it's actually needed. In the case of the image-loading example, using the proxy enables a more efficient use of resources because the high-resolution image is loaded only when the Display method is called.
This lazy loading mechanism provided by the proxy pattern can be particularly advantageous when dealing with resource-intensive operations or when you want to optimize the performance of your application by loading or creating objects on-demand rather than eagerly. It provides a level of control and flexibility that might not be present if you were to instantiate or load the real object immediately.
Example 2
Another common scenario where access control is important and the Proxy Pattern can be beneficial is in the implementation of a security proxy for sensitive operations.
using System;
// Subject interface
public interface IBankAccount
{
void Deposit(decimal amount);
void Withdraw(decimal amount);
decimal GetBalance();
}
// RealBankAccount class representing the actual bank account
public class RealBankAccount : IBankAccount
{
private decimal balance;
public RealBankAccount(decimal initialBalance)
{
this.balance = initialBalance;
}
public void Deposit(decimal amount)
{
balance += amount;
Console.WriteLine($"Deposited {amount}. New balance: {balance}");
}
public void Withdraw(decimal amount)
{
if (balance >= amount)
{
balance -= amount;
Console.WriteLine($"Withdrawn {amount}. New balance: {balance}");
}
else
{
Console.WriteLine("Insufficient funds.");
}
}
public decimal GetBalance()
{
return balance;
}
}
// ProxyBankAccount class representing a security proxy for the bank account
public class ProxyBankAccount : IBankAccount
{
private RealBankAccount realAccount;
private string username;
private string password;
public ProxyBankAccount(string username, string password)
{
this.username = username;
this.password = password;
}
private bool Authenticate()
{
// Simulate authentication logic (e.g., checking username and password)
return username == "validuser" && password == "password123";
}
private void CheckAccess()
{
if (!Authenticate())
{
throw new UnauthorizedAccessException("Invalid username or password.");
}
}
public void Deposit(decimal amount)
{
CheckAccess();
if (realAccount == null)
{
realAccount = new RealBankAccount(0); // Create the real object only when needed
}
realAccount.Deposit(amount);
}
public void Withdraw(decimal amount)
{
CheckAccess();
if (realAccount == null)
{
realAccount = new RealBankAccount(0);
}
realAccount.Withdraw(amount);
}
public decimal GetBalance()
{
CheckAccess();
if (realAccount == null)
{
realAccount = new RealBankAccount(0);
}
return realAccount.GetBalance();
}
}
class Program
{
static void Main()
{
// Client code uses ProxyBankAccount for secure access to the bank account
IBankAccount account = new ProxyBankAccount("validuser", "password123");
// Operations are performed through the proxy, which controls access
account.Deposit(1000);
account.Withdraw(500);
Console.WriteLine($"Current balance: {account.GetBalance()}");
}
}
In this example:
IBankAccount is the interface shared by RealBankAccount (the real object) and ProxyBankAccount (the security proxy).
RealBankAccount represents the actual bank account with deposit, withdrawal, and balance retrieval operations.
ProxyBankAccount performs access control by requiring authentication before allowing access to the real bank account. It acts as a security layer to control and monitor access to the sensitive operations.
This demonstrates how the Proxy Pattern can be used to enforce access control and add a layer of security around sensitive operations.
Comments
Post a Comment