Design Patterns - Proxy

Problem

Need to control access to a type for performance, security or other reasons.

Client should not know if they were calling the real service or a proxy.

Proxy also gives us time to do necessary things before sending request to real service and before sending response back to client.(like logging, caching, encrypt/decrypt…)

Proxy has similar structure as the Decorator Pattern, but the intent is different, Decorator pattern is for adding extra funcationalities to the original class whereas Proxy is focusing on control the access to the object.

This is another implementation of the Proxy Pattern, it doesn’t have the interface so we need to compose the RealService object in Proxy Class. One thing to notice is that the RealService properties and methods need to be marked as virtual for the Proxy Class to override them.

Proxy Variants

  • Virtual Proxy
    • stand in for expensive to create objects
  • Remote Proxy
    • Hide the detail to work with remote data or services.
  • Smart Proxy
    • Performs additional actions when a resource is accessed
  • Protective Proxy
    • controls access to a sensitive resource by checking for whether or not the client is authorized to perform those operations.

Virtual Proxy

Stands in for an expensive-to-create object. Typically responsible for getting real object. UI placeholders. Lazy-loaded Entity Properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ExpensiveToFullyLoad : BaseClassWithHistory
{
public static ExpensiveToFullyLoad Create()
{
return new VirtualExpensiveToFullyLoad();
}

public virtual IEnumerable<ExpensiveEntity> HomeEntities { get; protected set; }
public virtual IEnumerable<ExpensiveEntity> AwayEntities { get; protected set; }

protected ExpensiveToFullyLoad()
{
History.Add("Constructor called.");
}
}

When you have some expensive properties. You don’t want to create them when you don’t need them. So we could create a Proxy Class(VirtualExpensiveToFullyLoad), which will only create the property when its getting called.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class VirtualExpensiveToFullyLoad : ExpensiveToFullyLoad
{
public override IEnumerable<ExpensiveEntity> AwayEntities
{
get
{
if(base.AwayEntities == null)
{
base.AwayEntities = ExpensiveDataSource.GetEntities(this);
}
return base.AwayEntities;
}
protected set => base.AwayEntities = value;
}

public override IEnumerable<ExpensiveEntity> HomeEntities
{
get
{
if (base.HomeEntities == null)
{
base.HomeEntities = ExpensiveDataSource.GetEntities(this);
}
return base.HomeEntities;
}
protected set => base.HomeEntities = value;
}
}

When we test the class, we can see object history will only increase after we get the Entities from the class.

1
2
3
4
5
6
7
8
9
10
11
12
[Fact]
public void LogsCollectionLoadingToHistory()
{
var obj = ExpensiveToFullyLoad.Create();
var list = obj.HomeEntities;

Assert.Equal(2, obj.History.Count());

var anotherList = obj.AwayEntities;
Assert.Equal(3, obj.History.Count());

}

We could also use the C# Lazy<T> type which will handle the lazy instantiation and thread-safe for use

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LazyExpensiveToFullyLoad : BaseClassWithHistory
{
private Lazy<IEnumerable<ExpensiveEntity>> _homeEntities;
public IEnumerable<ExpensiveEntity> HomeEntities { get { return _homeEntities.Value; } }

private Lazy<IEnumerable<ExpensiveEntity>> _awayEntities;
public IEnumerable<ExpensiveEntity> AwayEntities { get { return _awayEntities.Value; } }

public LazyExpensiveToFullyLoad()
{
History.Add("Constructor called.");
_homeEntities = new Lazy<IEnumerable<ExpensiveEntity>>(() => ExpensiveDataSource.GetEntities(this));
_awayEntities = new Lazy<IEnumerable<ExpensiveEntity>>(() => ExpensiveDataSource.GetEntities(this));
}
}

Remote Proxy

Client works with proxy as if remote resource were local. Hides network details from client. Centralizes knowledge of network details.

Smart Proxy

Performs additional logic around resource access. Example: Resource counting, Cache management, Locking shared resources

Here we are trying to open the same file two times, normally this will throw an exception.

1
2
3
4
5
6
7
8
9
10
11
12
var fs = new FileSmartProxy();

byte[] outputBytes1 = Encoding.ASCII.GetBytes("1. ardalis.com\n");
byte[] outputBytes2 = Encoding.ASCII.GetBytes("2. weeklydevtips.com\n");
using var file = fs.OpenWrite(_testFile);
using var file2 = fs.OpenWrite(_testFile);

file.Write(outputBytes1);
file2.Write(outputBytes2);

file.Close();
file2.Close();

But we are using FileSmartProxy() Class, when we catch the exception, we will check if the file is already opened, and return the same reference to the file stream.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class FileSmartProxy : IFile
{
Dictionary<string, FileStream> _openStreams = new Dictionary<string, FileStream>();

public FileStream OpenWrite(string path)
{
try
{
var stream = File.OpenWrite(path);
_openStreams.Add(path, stream);
return stream;
}
catch (IOException)
{
if(_openStreams.ContainsKey(path))
{
var stream = _openStreams[path];

if(stream != null && stream.CanWrite)
{
return stream;
}
}
throw;
}
}
}

Protective Proxy

Manages access to a resource based on authorization rules. Eliminates repetitive security checks from client code and othe resource itself. Acts as a gatekeeper around a resource

Summary

If we are not using the Proxy Pattern, we often end up mixing the concerns of access control, or lazy loading or other funcationality in the resource class itself. Every client the consume this class must perform this work. The concerns of access control are mixed with the concerns of client or the resource. Proxy Pattern helps us to separate this.

Usually Proxy Pattern has built in class that support it.(Remote Proxy)

  • Decorator: the structure is similar, but the intent of Decorator Pattern is to add funcationality. Whereas the intent of Proxy Pattern is to control access.
  • Prototype: Prototype and Virtual Proxy Pattern both deal with objects that are expensive to create. But Virtual Proxy Pattern only provides a placeholder of the object and fetch it when required. The Prototype Pattern keeps a copy of the object on hand and can clone it when required.
  • Adapter: similar structure, but the intent of the Adapter Pattern is to convert an incompatible interface into one that works for the client.
  • Flyweight: designed to manage many reference to a shared instance.