Disciplined Agile

The Proxy Pattern

Contents


The Proxy Pattern


Proxy Pattern

Scott Bain discusses the essential features of the Proxy Pattern. Scott is a senior Disciplined Agile technical trainer and thought leader. This is part of a series of recorded presentations brought to you by PMI Disciplined Agile.

Contextual Forces


Motivation

We wish to add an optional behavior to an existing class. This new behavior may protect the class, or log access to it, or delay its behavior or instantiation, or any other single additional behavior. We also need to be able to use the existing class at times without this additional behavior.

Encapsulation

Whether the additional behavior is in place or not at any given point at runtime is hidden from the client... We may also wish to hide the fact that an optional behavior exists at all. Because the additional behavior is located in an additional object, the client is actually dealing with two objects when the proxy is in place, but only one when it is not. This difference is encapsulated as well (a limited form of the encapsulation of cardinality).

Procedural Analog

A simple condition to include or exclude an optional behavior in the midst of non-optional behavior

//Non-optional behavior here
if(condition) {
     // Optional Behavior here
}
//Non-optional behavior here

Non-Software Analog

I use a hand-held hose to water my garden. A few times a year, I add a container between the hose fitting and the watering head, which puts plant food into the water as it passes through.

Proxy hose

I take no different action when watering in this way, and yet there is additional behavior; feeding the plants. Also, various different plant foods could be introduced in this way (although only one at any given time), and again I would take no different action at watering time.

Implementation Forces


Example

proxy 1


public abstract class TargetAbstraction {
     public abstract ret m(par p);
{

public class BaseBehavior extends TargetAbstraction {
     public ret m(par p) {
           // un-proxied, or “Base” behavior
     }
}

public class Proxy extends TargetAbstraction {
     private BaseBehavior myBaseBehavior;
     public Proxy(BaseBehavior aBaseBehavior) {
           myBaseBehavior = aBaseBehavior;
     }
     public ret m(par p) {
           // Can add behavior before the base behavior
           myBaseBehavior.m(p);
           // Can add behavior after the base behavior
           return ret;
     }
}

Questions, concerns, credibility checks

  1. The Proxy and the BaseBehavior must have the same interface, so the client does not have to take any additional or different action when the Proxy is in place.
  2. If the BaseBehavior pre-exists, how will we get existing clients to use the Proxy, when appropriate, instead of the BaseBehavior?
  3. How will the Proxy get the reference to the Base Behavior? In the code example, the reference is passed in, but an object factory or other encapsulating entity can be used as well.

Options in implementation

The form shown above uses delegation from the proxy to the BaseBehavior class to reuse the pre-existing behavior. Another form of the Proxy (called the class Proxy) uses inheritance instead:

proxy 2

There are a number of different proxies, named for the behavior they add to class being proxied. Only some of them are possible with the class proxy form, but all of them are possible with the form that uses delegation.

Different Proxies

Some examples:

Logging Proxy: Logs all calls to the method of the BaseBehavior class, either before or after the BaseBehavior, or both.

Protection Proxy: Block access (or restrict/validate proper usage) to one or more methods in the BaseBehavior class. When those methods are called (or called in an invalid way), the Protection Proxy may return a null, or a default value, or throw an exception, etc…

Sychronization Proxy Ensure that multiple threads attempting to access the BaseBehavior will only be able to do so in a mutually exclusive way. That is, that multiple accesses will be serial, not parallel nor overlapping.

Perfecting Proxy: Ensure that all usage of the BaseBehavior class is correct, perfecting that usage as needed before delegating to the BaseBehavior class. For example, when an API requires a parameter is below x, a perfection proxy would reduce that parameter to x if it were too high, then delegate to the BaseBehavior class with the perfected value.

Remote Proxy: The Proxy resides in the same process space as the client object, but the BaseBehavior class does not. Hence, the Proxy contains the networking, piping, or other logic required to access the BaseBehavior object across the barrier. This cannot be accomplished with a class proxy.

Virtual Proxy: The Proxy delays the instantiation of the original object until its behavior is called for. If its behavior is never called for, then the original class is never instantiated. This is useful when the original class is heavyweight and/or there are a large number of instances needed. The Virtual Proxy is very lightweight. This cannot be accomplished with a class proxy.

Cache Proxy: The Proxy adds caching behavior to an object that represents a data source.

Etc…

 

Consequent Forces


API Design issues

A common question that arises in API design is the degree to which a client system should be trusted to use the API properly, and conversely the degree to which systems should guard their API's against invalid use.

As a simple example client system C calls service system S with paramter P. S requires that P must be an integer no greater than 1000. Should S check P and, for example, throw an exception if P exceeds the maximum?

api proxy

  • Sometimes C is a trusted entity, with its own tests that ensure that an invalid P cannot be passed.
  • Sometimes C is out of our control and we must specify the action to be taken in S if P is out of range.

If the situation is mixed (some clients are trusted, some are not) we can do the validation only when needed, by using a protection or perfection proxy only when the client is not trusted.

api proxy 2

Testing issues

The Proxy is specifically designed to delegate to the original class for all the base behavior, and thus the test should test this delegation, as well as the correct addition of behavior to the base behavior. This can be accomplished by replacing the base or original class with a Mock object:

api proxi 3

The Impact of Proxy on System Testing Issues

Often we need to break dependencies from the system under test to other entities, such as a database, user interface, network, etc... We do this by wrapping access to these entites in access objects, which can be mocked for testing.

For example, an object with a testable business rule may require a value from a database to complete its calculation. We can wrap access to the database with a Data Access Object, and mock that when testing the business rule's logic. This ensures that the tests will run fast, and that tests are not vulnerable to changes to the external system's state or structure. It also means we can test without access to the external system at all.

The DAO in this case we often refer to as a peripheral object. There may be many of these at the "edges" of the system under test. The obvious question arises, do we test peripheral objects?

We cannot test them in TDD, because they require access to actual external systems. They will be tested, but as part of integration tests that we do not run frequently, and are which not expected to run as fast as TDD tests. Because we will not test them frequently, however, it is advantageous to keep them as simple as possible, with no significant behavior in their implementation.

Thus, any signification behavior should be added via a proxy. Once this is done, the proxy can be tested against the same Mock object that the system under test uses.

api proxi dao

In the DAO example, if we wanted to add a cache to the access of the database, we'd use a caching proxy to do so.

Cost-Benefit (gain-loss)

  1. Proxies promote strong cohesion.
  2. Proxies simplify the client object and the object being proxied (by hiding complex issues like remoting and caching, etc…)
  3. If the instantiation of all classes is encapsulated by policy, inserting a proxy at a later time is significantly easier.
  4. Proxies often evolve into Decorators when multiple additional behaviors are needed. Knowing this, one does not have to introduce the Decorator until it is needed, avoiding overdesign and analysis paralysis.