Hi devs,
The Proxy Pattern is a common design pattern that many developers might use without even realizing it. It’s part of the Structural Design Patterns family and is used when you want to provide a surrogate or placeholder to control access to another object.
In simpler terms, the proxy pattern allows you to create an intermediary that controls access to a resource, such as an object that is expensive to create or requires security checks.
Let's explore this pattern through the lens of a payroll system. Imagine a scenario where calculating employee salaries requires fetching data from a remote service, and this process is both time-consuming and resource-intensive.
What is the Proxy Pattern?
The Proxy Pattern involves creating a proxy (intermediary) that represents another object, controlling access to it. This intermediary can help manage object creation, security, logging, and even caching.
Some common types of proxies:
- Virtual Proxy: Used to delay the creation of expensive objects until they are actually needed.
- Protection Proxy: Controls access to sensitive data, enforcing rules and permissions.
- Remote Proxy: Represents an object that exists in a remote location, like a server or API.
When to Use the Proxy Pattern?
In a payroll system, there could be several scenarios where the proxy pattern can be applied:
- Lazy Loading: Only calculate the payroll when required.
- Access Control: Restrict access to sensitive payroll data, only allowing authorized users to view or modify salary details.
- Logging and Auditing: Automatically log every time sensitive salary data is accessed or modified.
Example: Virtual Proxy in a Payroll System
Let’s say we have a payroll system where we want to calculate employee salaries. Fetching this data is expensive because it requires contacting an external API. We don’t want to fetch the data until it’s absolutely necessary.
Here’s how we can apply the Proxy Pattern to solve this issue:
Step 1: Define the Interface
// Payroll interface that defines the method to get employee salary
public interface IPayroll
{
decimal GetSalary();
}
Step 2: Implement the Real Payroll Calculation
// Real Payroll class that does the actual salary calculation
public class RealPayroll : IPayroll
{
private string _employeeId;
public RealPayroll(string employeeId)
{
_employeeId = employeeId;
// Simulate expensive operation (e.g., fetching salary details from an API)
Console.WriteLine("Fetching salary details from the payroll system...");
}
public decimal GetSalary()
{
// Simulate the salary calculation for the employee
return 5000.00M; // Example salary
}
}
Step 3: Implement the Proxy
// Proxy class to control access to RealPayroll
public class PayrollProxy : IPayroll
{
private RealPayroll _realPayroll;
private string _employeeId;
public PayrollProxy(string employeeId)
{
_employeeId = employeeId;
}
public decimal GetSalary()
{
// Only create RealPayroll when needed (lazy initialization)
if (_realPayroll == null)
{
_realPayroll = new RealPayroll(_employeeId);
}
return _realPayroll.GetSalary();
}
}
Step 4: Client Code
// Client code
class Program
{
static void Main(string[] args)
{
IPayroll payroll = new PayrollProxy("EMP123");
// Salary is fetched and calculated only when requested
Console.WriteLine("Proxy created, but salary not fetched yet.");
Console.WriteLine($"Employee Salary: {payroll.GetSalary()}"); // Salary calculation triggered
Console.WriteLine($"Employee Salary: {payroll.GetSalary()}"); // Cached result, no new fetch
}
}
Output:
Proxy created, but salary not fetched yet.
Fetching salary details from the payroll system...
Employee Salary: 5000.00
Employee Salary: 5000.00
Explanation:
- RealPayroll: This class performs the actual salary calculation, but since it simulates an expensive operation, we don’t want it to run until necessary.
-
PayrollProxy: This class controls when the real payroll calculation is triggered. The first time
GetSalary()
is called, it fetches the data. Subsequent calls reuse the result. - Lazy Loading: With the proxy, the real object is only created when it is needed, helping save resources.
Example: Protection Proxy in a Payroll System
Now, let’s imagine we need to restrict access to salary data based on roles (e.g., only managers can view salaries). We can use a Protection Proxy to enforce access control.
Step 1: Define the Interface
public interface IEmployeeSalary
{
decimal GetSalary();
}
Step 2: Implement the Real Payroll
public class EmployeeSalary : IEmployeeSalary
{
private decimal _salary;
public EmployeeSalary(decimal salary)
{
_salary = salary;
}
public decimal GetSalary()
{
return _salary;
}
}
Step 3: Implement the Protection Proxy
public class SalaryProtectionProxy : IEmployeeSalary
{
private EmployeeSalary _realSalary;
private string _role;
public SalaryProtectionProxy(EmployeeSalary realSalary, string role)
{
_realSalary = realSalary;
_role = role;
}
public decimal GetSalary()
{
if (_role != "Manager")
{
throw new UnauthorizedAccessException("Access Denied: Only managers can view employee salary.");
}
return _realSalary.GetSalary();
}
}
Step 4: Client Code
class Program
{
static void Main(string[] args)
{
EmployeeSalary salary = new EmployeeSalary(6000.00M);
IEmployeeSalary proxy = new SalaryProtectionProxy(salary, "Employee");
try
{
Console.WriteLine($"Salary: {proxy.GetSalary()}");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine(ex.Message);
}
}
}
Output:
Access Denied: Only managers can view employee salary.
Explanation:
In this example, the SalaryProtectionProxy ensures that only managers can access employee salary data. This prevents unauthorized users from viewing sensitive information.
Conclusion
The Proxy Pattern is a versatile tool that can help developers manage access to resources, improve system performance, and enforce security policies. In a payroll system, this pattern allows for flexible, scalable design by implementing lazy loading, caching, and access control without complicating the core business logic.
Whether you need to control access to sensitive payroll data or delay the creation of expensive objects, the Proxy Pattern can help simplify your code and make it more maintainable.
Keep coding :)