About the Proxy Pattern

Edit: Added an update with a more concrete example further down.

I just wanted to share and provide a further explanation on the Proxy Pattern example I gave at the Ann Arbor PHP/MySQL User Group today.

Source: https://gist.github.com/4302634

Now, Dan had mentioned that this could be done through simple inheritance instead of via interface contract, and after some thought, I realized that I disagree.

Typically with an API object, you will initialize with a remote URL, and perhaps a token:

public function __construct($api_url, $auth_token)

But remember our new Cache API proxy only takes an API object and a Cache object.

public function __construct($cache, $api)

This fundamentally changes the contract that the initial API object creates, and as such, should then NOT extend the original API implementation.

Part of doing OOP well involves the management of contracts we establish. Changing the rules of a function in a sub-classed entity violates the Liskov substitution principle (as referenced in SOLID object oriented design).

Now, you COULD subclass the MyApi class, and add the Cache control via setter injection, but personally I don’t think that implementation is nearly as clean as providing a proxy object to add the caching functionality.

 

p.s. I’ll leave QuickTime running but I think it did eat the recording of the presentation — sorry guys! I’ll put the slides up soon but I don’t know how much context they will provide given how light they were compared to the commentary that went along with it.

 

Update (12/07/2012):

It has been pointed out that my example was perhaps a little too contrived, so I think I found a better one.

Let’s say you’re building a system out, and you know you want logging, but you don’t know what sort of implementation you want to do for said logging. Given that instance, let’s start with our basic interface:

1
2
3
4
5
6
interface Logger
{
  public function info($message);
  public function debug($message);
  public function fatal($message);
}

Ok, great! So, because production schedules are tight, we decide to give a very basic implementation first:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyBasicLogger implements Logger
{
  public function info($message)
  {
    error_log('INFO: '.$message);
  }
 
  public function debug($message)
  {
    error_log('DEBUG: '.$message);
  }
 
  public function fatal($message)
  {
    error_log('FATAL: '.$message);
  }
}

Alright! We now have our basic logger implemented!

Oh, what’s that you say? You want to use Monolog in place of error_log() in production? sure!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MyMonologLogger implements Logger
{
  protected $monolog;
 
  public function __construct($monolog)
  {
    $this->monolog = $monolog;
  }
 
  public function info($message)
  {
    $this->monolog->addInfo($message);
  }
 
  public function debug($message)
  {
    $this->monolog->addDebug($message);
  }
 
  public function fatal($message)
  {
    $this->monolog->addCritical($message);
  }
}

Now we have two ways of logging messages. Now comes the constraint that we want to be emailed when fatal errors are logged. Your first instinct would be to extend MyMonologLogger and add it by overloading fatal(), but then we can’t use that functionality on any other logger we ever build. How do we build this functionality in a way that we can reuse over and over again? The Proxy pattern.

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
class MyEmailingLoggerProxy implements Logger
{
  protected $logger;
 
  public function __construct(Logger $logger)
  {
    $this->logger = $logger;
  }
 
  public function fatal($message)
  {
    $this->logger->fatal($message);
    mail([email protected]', 'A fatal error has occurred', $message);
  }
 
  public function info($message)
  {
    $this->logger->info($message);
  }
 
  public function debug($message)
  {
    $this->logger->debug($message);
  }
}

And now, no matter what we choose as our logging backend, now or in the future, we will always easily be able to have those fatal errors emailed to us by simply putting our chosen logging system inside an instance of MyEmailingLoggerProxy.

I hope this clears some things up!

12 Comments

  1. meze

    If you read about LSP it says ” objects of type S may be substituted for objects of type T”. Constuctors aren’t a part of an object’s API. They are “factories” for objects.
    For example, if you have a method public function setApi(TwitterApi $api), you can substitute $api with Cache API that extends the original class. setApi method doesn’t care at all (unless you do something really silly like $api->__construct()) what constructor signature is, so it doesn’t violate LSP and your agument becomes invalid…

    Reply

    • Jacob Mather

      You are correct with this specific instance — I have added another example where the reasoning is a little more straight forward.

      However even with the poor prior example, if you do this via inheritance instead of via composition, the cache then technically becomes a fundamental piece of that object hierarchy, which it actually has zero relation to, and I feel an implementation built on inheritance would still violate the general separation of concerns you want to enforce to keep code simple and easy to maintain further down the line.

      Thank you so much for the feedback!

      Reply

  2. cordoval

    would be good to know a real case of this problem

    Reply

    • Jacob Mather

      I hope my new example helps expand on a real-world scenario.

      Reply

  3. Dan Trenz

    Great post.

    I must admit I’m not completely clear on why “implements” > “extends”.

    How is your example – “MyEmailingLoggerProxy implements Logger” – better than simply extending the base class (i.e. “MyEmailingLoggerProxy extends MyBasicLogger”)?

    Forgive me, I’m not well-versed in OOP design patterns, so I’m trying to understand the concrete benefits of using interfaces.

    Reply

    • Jacob Mather

      Dan,

      I totally get where you’re coming from. Let me try and re-explain it within the example I gave.

      The question you’re really asking is called composition over inheritance. The basic idea is when we extend, we want to keep within a subset of the main object.

      As email is about notification, and not logging, it doesn’t make sense to extend our emailing facility from one of our base logging objects.

      The other artifact, is that if we do extend MyEmailingBasicLogger from MyBasicLogger, we would then also have to create MyEmailingMonologLogger which has the exact duplicate functionality.

      Extracting the emailing logic away form the My*Logger object hierarchy reduces complexity in the over-all system design, and increases flexibility.

      Say tomorrow you decide you not only want to email on critical failure, but automatically open a bug in your issue tracker, you could then make MyIssueTrackerLoggerProxy in the same vein as MyEmailingLoggerProxy. Then you would do something like:

      $logger = new MyIssueTrackerLoggerProxy(new MyEmailingLoggerProxy(MyBasicLogger()));

      Any system you have that relies on a Logger interfaced object can continue to use $logger the same way it always has, and you can dynamically add new functionality on top of it without changing any expectations.

      Is that a little clearer? Or did I just muddy the water more?

      Reply

  4. designbymobius

    Good read. Basically a way to keep your system structure decoupled.
    Need to try a new logger? Simple!

    1. Implement original logger in a new class
    2. New class uses its process and method and wraps them in logger’s original endpoints
    3. Profit!

    I know this is a PHP blog but it would be awesome to see something like this for javascript …

    Reply

    • Jacob Mather

      Right.

      This isn’t necessarily a PHP blog, it’s just a blog on stuff I work on (and things that interest me), which does also include JavaScript.

      You can do all of this in JavaScript, though there are no actual contractual constraints on it. I believe this is part of the reason some of the JavaScript community is pushing CoffeeScript, as it adds a compilation step where such constraints can be tested against, giving JavaScript a little more rigidity.

      Reply

      • designbymobius

        I managed something like this in a way I’m not proud of but gets the job done. I enclosed separate systems into objects and communicated between them with predefined endpoints. Something like this:

        var playerEngine = {};

        (function(x){

        // change currently playing track
        x.changeTracks = function(trackMetadata) {
        // action here
        renderEngine.renderNewTrack(trackMetadata);
        }

        }(x))(playerEngine);

        var renderEngine = {};

        (function(x){

        // update UI with current playing track
        x.renderNewTrack = function(trackMetadata) {
        // action here
        }

        }(x))(renderEngine);

        What this allowed me to do was separate the UI from the actual internals, so when I needed to change UI in a different version of the app, all I did was change renderEngine. Same endpoint to the main app. Solution has so many problems with it from an architectural standpoint, but it currently gets the job done so it’s what I still use.

        Searching around for better ways of doing it though. Thanks for the reply, JM

        Reply

        • Jacob Mather

          Sometimes working trumps elegant. Especially with JavaScript. 🙂

          Reply

    • Jon

      I think that explanation is finally resonating with me. I’m also struggling with the need for interfaces but I agree with both the points you make here.

      Reply

      • Jacob Mather

        Heh, what comes to mind with that is a great quote from Canopy (the parent company of SCO — who went suing people willy-nilly ~10 years ago over linux stuff?):

        “Contracts are what you use against people you have relationships with.”

        Interfaces are contracts you make with your code. They let you guarantee a specific set of functionality.

        Reply

Leave a Reply

*

twitter