Proxy Pattern

10 November 2017

10 November 2017 Patterns Ruby

Patterns Ruby Proxy Pattern

Subscribe to receive new articles. No spam. Quality content.

Follow @makagon

In this article, we will cover Proxy pattern and its types. We will implement each of them using Ruby.

Intent

Let's begin with the intent of this pattern. In "Design Patterns: Elements of Reusable Object-Oriented Software" book they describe the intent of Proxy as:

Provide a surrogate or placeholder for another object to control access to it

This pattern is also known as Surrogate.

I like the example they provide in that book:

Consider a document editor that can embed graphical objects in a document. Some graphical objects, like large raster images, can be expensive to create. But opening a document should be fast, so we should avoid creating all the expensive objects at once when the document is opened. [...] The solution is to use another object, like image proxy, that acts as a stand-in for the real image. The proxy acts just like the image and takes care of instantiating it when required.

Dependencies would look like this:

TextDocument would use ImageProxy to show some placeholder initially, and ImageProxy would load actual Image when required.

Applicability

Proxy is useful whenever there is a need for a more sophisticated reference to an object. It's applicable as:

Virtual proxy Protection proxy Remote proxy Smart reference

Virtual Proxy

Creates expensive objects on demand

Example with ImageProxy described above is exactly this type of proxy. Let's implement it with Ruby.

We have TextDocument class with 2 methods:

class TextDocument attr_accessor :elements def load elements.each { |el| el.load } end def render elements.each { |el| el.render } end end

It has just two methods: load and render . We assume that we want to load all elements for the document and then we want to render them.

Let's say that we have Image element that takes too long to load:

class Image def load # ... takes too much time end def render # render loaded image end end

So if we want to load document with image:

document = TextDocument.new document.elements.push(Image.new) document.load # => takes too much time because of image

It will take too long because of the load time of the image.

We can create a virtual proxy for an image that would implement lazy loading:

class LazyLoadImage attr_reader :image def initialize(image) @image = image end def load end def render image.load image.render end end

Now if we use LazyLoadImage proxy, it will not hold document loading. It happens because LazyLoadImage doesn't load image until render call.

document = TextDocument.new image = Image.new document.elements.push(LazyLoadImage.new(image)) document.load # => fast document.render # => slow because image is being loaded

We could use SimpleDelegator to implement proper delegation from LazyLoadImage to Image as we did for Decorator Pattern.

Protection Proxy

A protection Proxy controls access to the original object

This one is pretty obvious, if you want to apply some protection rules before calling the original object, you can wrap it in a Protection Proxy

class Folder def self.create(name) # creating folder end def self.delete(name) # deleting folder end end class FolderProxy def self.create(user, folder_name) raise 'Only Admin Can Create Folders' unless user.admin? Folder.create(folder_name) end def self.delete(user, folder_name) raise 'Only Admin Can Delete Folders' unless user.admin? Folder.delete(folder_name) end end

I must admit that in this example we have a different interface between Proxy and original class. Folder accepts just one param for create and delete , whereas FolderProxy accepts user as well. I'm not sure if it's the best implementation of this type of proxy. Let me know in comments if you have better example ;)

Remote Proxy

A remote proxy provides a local representative for an object in different address space

For example, if you use remote procedure calls (RPC), you can easily create Proxy that would handle RPC calls. I'll use xml-rpc gem for example.

To make a remote procedure call we can use this code:

require 'xmlrpc/client' server = XMLRPC::Client.new2("http://myproject/api/user") result = server.call("user.Find", id)

Let's create Remote Proxy that would handle it for us:

class UserProxy def find(id) server.call("user.Find", id) end private def server @server ||= XMLRPC::Client.new2("http://myproject/api/user") end end

Now we have Proxy that we can use to get access to an object in a different address space.

Smart Reference

A smart reference is a replacement for a bare pointer that performs additional actions when an object is accessed

One of the usages is to load a persistent object into memory when it's first referenced.

Using this type of Proxy we can create Memoization for response.

Let's say that we have a third-party tool that does some heavy calculation for us. Usually, they would provide a gem for us. Let's imagine that it looks like this:

class HeavyCalculator def calculate # takes some time to calculate end end

Because it's third-party gem, we can not add memoization there. At the same time, we don't want to wait too long for every call to HeavyCalculator . In this case, we can create a Smart Reference proxy that would add memoization for us:

class MemoizedHeavyCalculator def calculate @result ||= calculator.calculate end private def calculator @calculator ||= HeavyCalculator.new end end

Now we can use MemoizedHeavyCalculator to call calculate as many times as we need, and it will make the actual call just once. For any further call, it will use memoized value.

Using Proxy as a smart reference, we could add logging as well. For example: if we have third-pary service that provides some quotes for us (we can not change it's code) and we want to add logging for each call to that service. We could implement it this way:

class ExpensiveService def get_quote(params) # ... sending request end end class ExpensiveServiceWithLog def get_quote(params) puts "Getting quote with params: #{params}" service.get_quote(params) end private def service @service ||= ExpensiveServiceWithLog.new end end

We can not change the implementation of ExpensiveService because it's a third-party code. But using ExpensiveServiceWithLog we can add any sort of logging we need. Just for sake of simplicity, I used puts there.

Related Patterns

Some implementations of a Proxy pattern are really similar to implementation of Decorator Pattern. But these two patterns have different intent. A Decorator adds responsibilities to an object, whereas proxy controls access to an object.

A Proxy might look similar to Adapter pattern. But adapter provides a different interface to the object it adapts. A Proxy provides the same interface as its subject.

PS: the proxy object should respond to all methods of the original object. In examples above, I've just implemented methods of the original object in the proxy class. The original object could have many methods and it would be a lot of repetitive code in proxy class.

Since implementation of Proxy pattern and Decorator pattern is almost the same, I highly recommend you to read about SimpleDelegator which helps to delegate methods from proxy to original object.

Thanks for reading!

Subscribe to receive new articles. No spam. Quality content.

Follow @makagon