The proxy pattern is used to solve access related challenges, you can think of a proxy as a bouncer to a nightclub, where the bouncer is the proxy and the nightclub is the resource.
there are three generally accepted types of the proxy pattern:
- Remote Proxy: a remote proxy is used when you have to retrieve something or send a command outside of your codebase boundary, so for example to a remote server or a different project within your solution. Your proxy would be a the local representation of the remote resource, this representation would most likely modify add some sort of additional behavior.
- Virtual Proxy: a virtual proxy controls access to a resource that is expensive to create; almost like a high level lazy loading of a resource.
- Protection Proxy: a protection proxy can be thought of as the guardian of a resource; access to a particular resource can be restricted by having to have to go through a "Protection proxy" which controls access to the resource that it's protecting based on access rights.
The idea behind a proxy is that you have a resource, but before you access the resource you want add some sort of additional behavior with the intent of controlling access to the underlying resource without modifying the resource.
the following is a general UML representation of the Proxy pattern, based on what role the proxy is fulfilling and where it is located would depend on which type of proxy pattern you'd be leveraging.
In the above we see that our client class maintains a reference to the IResource interface. Both our Resource and it's proxy implement the IResource interface, however our proxy also maintains a reference to our Resource implementation, this way we can leverage our resource within our proxy as well as extend functionality.
Remote-ish proxy pattern
Let's take a look at a more concrete example; contrived but a bit more concrete. We're going to create a line of credit provider project and a high risk lender project; the lender project is going to have a proxy line of credit that will represent a remote resource in our provider project. the rational behind this is that our lender project would represent a 3rd party lender that a high risk borrower would interact with and our provider would supply that high risk lender with lines of credit.
above we have our solution, with our two project and a reference form our lender project to our provider project. next let's take a look at our provider project.
namespace pav.ProxyPattern.RemoteResource
{
public interface ILineOfCreditQuote {
double Limit { get; }
double Rate { get; }
void ModifyLimit(double amount);
void ModifyRate(double amount);
}
public class LineOfCreditQuote : ILineOfCreditQuote {
public double Limit { get; private set; }
public double Rate { get; private set; }
public void
ModifyLimit(double amount)
=> Limit += amount;
public void
ModifyRate(double amount) => Rate +=
amount;
public LineOfCreditQuote(double limit, double rate) {
this.Limit = limit;
this.Rate = rate;
}
}
}
now it's fairly straight foreword we created an interface that represents a line of credit and a concrete implementation. Next let's take a look at the code for our lender.
using pav.ProxyPattern.RemoteResource;
using System;
namespace pav.ProxyPattern
{
class LineOfCreditRemoteProxy : ILineOfCreditQuote
{
ILineOfCreditQuote loc { get; set; }
public double Limit
=> loc.Limit;
public double Rate
=> loc.Rate;
public void
ModifyLimit(double amount)
{
if (amount < 0)
ModifyRate(-amount / (amount *
100));
else
ModifyRate(+amount/(amount *
100));
loc.ModifyLimit(amount);
}
public void
ModifyRate(double amount) =>
loc.ModifyRate(amount);
public LineOfCreditRemoteProxy(ILineOfCreditQuote loc)
{
loc.ModifyRate(0);
this.loc = loc;
}
public void
Display() => Console.WriteLine($"Limit:{Limit.ToString("C")} Rate:{Rate.ToString("P2")}");
}
class Program
{
static void Main(string[] args)
{
var one = CreateLineOfCredit(100000, true, false);
one.Display();
one.ModifyLimit(10000);
one.Display();
Console.WriteLine();
var two = CreateLineOfCredit(150000, false, true);
two.Display();
two.ModifyLimit(-5000);
two.Display();
Console.WriteLine();
CreateLineOfCredit(80000, false, false).Display();
}
static LineOfCreditRemoteProxy CreateLineOfCredit(double income, bool mortgage, bool pastBankruptcy)
{
var limit = income * .1;
var rate = .07 - (mortgage ? 1 : 0) * .005 + (pastBankruptcy ? 1 : 0)
* .03;
return new
LineOfCreditRemoteProxy(new
LineOfCreditQuote(limit, rate));
}
}
}
also no magic here in our program we created a CreateLineOfCredit function that is basically a factory method to create lines of credit with some light business logic for setting the line of credit rate.
we created a LineOfCreditProxy class that takes in a provider's line of credit object and then modifies it with some of our high risk lender business logic.
our program then creates lines of credit using our factory method and wraps the provider based lines of credit with our high risk business logic.
now this is an extremely contrived and bullshit example, but i'd like to think that it gets the idea across.
The main point to take away for me is that a remote proxy is a local representation of a remote resource.
Virtual Proxy
In this example we are going to create a virtual proxy, the virtual proxy is an implementation of deferred instantiation, it tackles the problem of instantiating an expense object, so for example let's say that we had an Interest Calculator that for some reason needed a ton of memory and supporting classes that would take significant time to instantiate, but we may or may not ever call the interest calculation, well it would be rather silly to exert that much effort for something we may never use. so let's tackle this problem using the proxy pattern.
first lets take a loot at our interface.
interface IInterestCalculator
{
double Income { get; }
double Savings { get; }
double Mortgage { get; }
double GetLoanInterest(double amount);
}
now that speaks for itself, we have three properties that will be used to generate a interest rate based on a requested loan amount.
next let's take a look at our concrete implementation of our interest calculator.
class InterestCalculator : IInterestCalculator
{
public double Income {
get; private set; }
public double Savings
{ get; private set; }
public double Mortgage
{ get; private set; }
public double
GetLoanInterest(double amount)
=> ((amount - Savings) / Income +
(Mortgage > 0 ? .1 : 0))/100;
public InterestCalculator(double income, double savings,
double mortgage)
{
this.Income = income;
this.Savings = savings;
this.Mortgage = mortgage;
//long running thread to confirm parameters
Thread.Sleep(10000);
}
}
Now instead of implementing some sort of long running validation process we just sleep for 10 seconds to get the point across.
Next let's take a look at our proxy class.
class InterestCaluclatorProxy : IInterestCalculator
{
IInterestCalculator InterestCaluclator;
double income;
public double Income
=> this.InterestCaluclator?.Income ??
income;
double savings;
public double Savings
=> this.InterestCaluclator?.Savings ??
savings;
double mortgage;
public double Mortgage
=> this.InterestCaluclator?.Mortgage ??
mortgage;
public double GetLoanInterest(double amount)
{
if (this.InterestCaluclator
== null)
this.InterestCaluclator = new InterestCalculator(income, savings, mortgage);
return this.InterestCaluclator.GetLoanInterest(amount);
}
public InterestCaluclatorProxy(double income, double savings,
double mortgage)
{
this.income = income;
this.savings = savings;
this.mortgage = mortgage;
}
public void
Display()
{
Console.WriteLine($"Income\t:{Income.ToString("C")}");
Console.WriteLine($"Savings\t:{Savings.ToString("C")}");
Console.WriteLine($"Mortgage:{Mortgage.ToString("C")}");
}
}
in our proxy class, we instantiate it with the properties required for our calculation, but we defer creating the underlying implementation of the Interest calculator to such a time as we need to make our calculation.
and finally let's take a look at our client application
class Program
{
static void Main(string[] args)
{
var proxy = new
InterestCaluclatorProxy(100000, 10000, 0);
proxy.Display();
Console.WriteLine(proxy.GetLoanInterest(250000).ToString("P2"));
}
}
and volia we can instantiate our proxy, execute the diplay method which will print out our properties, but we wont see that 10s delay until we call the get loan interest function.
Protection Proxy
the third type of proxy is a protection proxy, this proxy is used to control access to a resource based on some sort of access rights or business logic, i'm going to steal borrow this example from a Wikipedia article, because i'm too tired to think of one myself. let's create two: classes car and driver, now we know that you have to be at least 16 to drive a car so let's make a proxy car to enforce that rule.
to get started lets take a look at our ICar interface
interface ICar
{
void DriveCar();
}
doesn't get simpler than that. Next lets look at our concrete implementation of our ICar interface
public class Car : ICar
{
public void
DriveCar() => Console.WriteLine("Car has
been driven!");
}
easy enough, before we look at our proxy car let's create a driver class
public class Driver
{
public int Age { get; private set; }
public Driver(int age)
=> this.Age = age;
}
all we really care is the drivers age. next let's take a look at our proxy car class.
public class ProxyCar : ICar
{
Driver driver;
ICar car;
public ProxyCar(Driver driver)
{
this.driver = driver;
this.car = new Car();
}
public void
DriveCar()
{
if (driver.Age < 16)
Console.WriteLine("Sorry, the driver is too young to drive.");
else
this.car.DriveCar();
}
}
we see that our proxy car has both an implementation of ICar and a reference to it; this allows our proxy to have a reference to a concrete (real) implementation of ICar as well as allow us to add access functionality in this case an age verification before the car can be driven.
so if i was going to summarize these three proxy types it would be as follows:
Remote proxy is a local representation of a remote resource that may or may not append functionality and or modify existing functionality of the resource it's proxying.
Virtual proxy is a differed instantiation of an expensive resource that also implements a caching strategy
Protection proxy is a proxy that appends access control rights to an existing resource, often times extending it's functionality.