Proxy pattern in C#

Hi! Today, I’m going to talk about the Proxy pattern and how it can be used in C# 😊.

The Proxy pattern is a structural design pattern that provides a surrogate or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object.

Why would you want to use a proxy? Well, there are several reasons, such as:

  • Lazy initialization: You can create a proxy to delay the creation of a heavy object until it’s actually needed.
  • Remote access: You can create a proxy to represent a remote object and handle network communication behind the scenes.
  • Access control: You can create a proxy to check the permissions of clients before allowing them to access the original object.
  • Logging: You can create a proxy to log the requests and responses of the original object.

In C#, you can implement the Proxy pattern by creating an interface that defines the common methods for both the original object and the proxy. Then, you can create a concrete class that implements the interface and represents the original object. Finally, you can create another concrete class that also implements the interface and acts as the proxy. The proxy should have a reference to the original object and delegate all the work to it, while adding some extra behavior before or after.

In this article, we will focus on the last use case: logging. Suppose you have a financial application that uses an interface called IAccount to represent different types of accounts, such as checking, saving, or credit accounts. Each account has methods to get the balance, deposit money, or withdraw money. You want to add logging functionality to these methods, so that you can track the transactions and detect any errors or frauds.

One way to do this is to modify each account class and add logging code to each method. However, this would violate the open-closed principle, which states that classes should be open for extension but closed for modification. Moreover, this would introduce duplication and coupling between the account classes and the logging mechanism.

A better way to do this is to use the Proxy pattern. You can create a proxy class that implements the IAccount interface and has a reference to an account object. The proxy class can delegate all the work to the account object, while adding logging code before or after each method call. This way, you can keep the account classes unchanged and decouple them from the logging mechanism.

Here is an example of how you can implement the Proxy pattern in C# for logging:


// The interface that defines the common methods for both
// the account classes and the proxy class
public interface IAccount
{
decimal GetBalance();
void Deposit(decimal amount);
void Withdraw(decimal amount);
}


// A concrete class that implements the interface and
// represents a checking account
public class CheckingAccount : IAccount
{
private decimal balance;

public CheckingAccount(decimal initialBalance)
{
balance = initialBalance;
}

public decimal GetBalance()
{
return balance;
}

public void Deposit(decimal amount)
{
balance += amount;
}

public void Withdraw(decimal amount)
{
balance -= amount;
}
}

// A concrete class that implements the interface and
// represents a saving account
public class SavingAccount : IAccount
{
private decimal balance;
private decimal interestRate;

public SavingAccount(decimal initialBalance, decimal interestRate)
{
balance = initialBalance;
this.interestRate = interestRate;
}

public decimal GetBalance()
{
return balance;
}

public void Deposit(decimal amount)
{
balance += amount;
}

public void Withdraw(decimal amount)
{
balance -= amount;
}

// A specific method for saving accounts
public void AddInterest()
{
balance += balance * interestRate;
}
}


// A concrete class that also implements the interface and
// acts as a proxy for an account object
public class AccountProxy : IAccount
{
// The reference to the real account object
private IAccount account;
// The path of the log file
private string logFile;

public AccountProxy(IAccount account, string logFile)
{
this.account = account;
this.logFile = logFile;
}

public decimal GetBalance()
{
// Logging: write some information before calling the method
WriteLog($"Getting balance of {account.GetType().Name}");

// Delegate the work to the real account object
decimal balance = account.GetBalance();

// Logging: write some information after calling the method
WriteLog($"Balance is {balance}");

return balance;
}

public void Deposit(decimal amount)
{
// Logging: write some information before calling the method
WriteLog($"Depositing {amount} to {account.GetType().Name}");

// Delegate the work to the real account object
account.Deposit(amount);

// Logging: write some information after calling the method
WriteLog($"Deposit successful");
}

public void Withdraw(decimal amount)
{
// Logging: write some information before calling the method
WriteLog($"Withdrawing {amount} from {account.GetType().Name}");

// Delegate the work to the real account object
account.Withdraw(amount);

// Logging: write some information after calling the method
WriteLog($"Withdrawal successful");
}

// A helper method to write to the log file
private void WriteLog(string message)
{
using (StreamWriter writer = new StreamWriter(logFile, true))
{
writer.WriteLine($"{DateTime.Now}: {message}");
}
}
}


To use this example, you can create an instance of AccountProxy and pass it an account object and a log file path. Then, you can call the methods of the proxy as if it was the real account object. The proxy will log the requests and responses to the log file.


// Create a checking account with 1000 initial balance
IAccount checking = new CheckingAccount(1000);

// Create a saving account with 500 initial balance and 0.01 interest rate
IAccount saving = new SavingAccount(500, 0.01m);

// Create proxies for both accounts and specify the log file path
IAccount checkingProxy = new AccountProxy(checking, "checking.log");
IAccount savingProxy = new AccountProxy(saving, "saving.log");

// Use the proxies to perform some operations on the accounts
Console.WriteLine($"Checking balance: {checkingProxy.GetBalance()}");
Console.WriteLine($"Saving balance: {savingProxy.GetBalance()}");

checkingProxy.Deposit(200);
savingProxy.Deposit(100);

Console.WriteLine($"Checking balance: {checkingProxy.GetBalance()}");
Console.WriteLine($"Saving balance: {savingProxy.GetBalance()}");

checkingProxy.Withdraw(500);
savingProxy.Withdraw(200);

Console.WriteLine($"Checking balance: {checkingProxy.GetBalance()}");
Console.WriteLine($"Saving balance: {savingProxy.GetBalance()}");


The output of this program will be:


Checking balance: 1000
Saving balance: 500
Checking balance: 1200
Saving balance: 600
Checking balance: 700
Saving balance: 400


The log files will contain the following information:


// checking.log

8/23/2022 12:00:00 PM: Getting balance of CheckingAccount
8/23/2022 12:00:00 PM: Balance is 1000
8/23/2022 12:00:01 PM: Depositing 200 to CheckingAccount
8/23/2022 12:00:01 PM: Deposit successful
8/23/2022 12:00:01 PM: Getting balance of CheckingAccount
8/23/2022 12:00:01 PM: Balance is 1200
8/23/2022 12:00:02 PM: Withdrawing 500 from CheckingAccount
8/23/2022 12:00:02 PM: Withdrawal successful
8/23/2022 12:00:02 PM: Getting balance of CheckingAccount
8/23/2022 12:00:02 PM: Balance is 700

// saving.log

8/23/2022 12:00:00 PM: Getting balance of SavingAccount
8/23/2022 12:00:00 PM: Balance is 500
8/23/2022 12:00:01 PM: Depositing 100 to SavingAccount
8/23/2022 12:00:01 PM Deposit successful
8/23/2022 12:00:01 PM Getting balance of SavingAccount
8/23/2022 12.00.01 PM Balance is 600
8/23/2022 12.00.02 PM Withdrawing 200 from SavingAccount
8/23/2022 12.00.02 PM Withdrawal successful
8/23/2022 12.00.03 PM Getting balance of SavingAccount
8/23/2022 12.00.03 PM Balance is 400

As you can see, the Proxy pattern allows you to add some extra functionality to an existing object without changing its interface. This can be useful for various scenarios, such as lazy initialization, remote access, access control, logging, and more.

I hope you enjoyed this article and learned something new about the Proxy pattern in C#. If you want to learn more about design patterns in C#, you can check out these resources:


Thank you for reading and happy coding! šŸ˜„


You can find the code examples here: source code


If you liked this article, please share it with your friends and colleagues. You can also leave a comment below and let me know what you think. I would love to hear your feedback and suggestions.

Comments

Popular posts from this blog

Which GOF patterns are good for C#?

Angular on a regular SharePoint page (part 2)

Chain of Responsibility pattern in C#