Proxy Pattern

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

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:

Ruby - image proxy

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:

  1. Virtual proxy
  2. Protection proxy
  3. Remote proxy
  4. 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.

Comments