Friday, October 2, 2009

Patterns - 029: Structural patterns ( Bridge Proxy )

The Bridge pattern promotes the separation of an abstraction’s interface from its implementation. In general, the term abstraction refers to the process of identifying the set of attributes and behavior of an object that is specific to a particular usage.
This specific view of an object can be designed as a separate object omitting irrelevant attributes and behavior. The resulting object itself can be referred to as an abstraction.
Note that a given object can have more than one associated abstraction, each with a distinct usage.
Abstraction implementers
(figure abstraction implementers)
Both Implementer_1 and Implementer_2 represent the set of Abstraction implementers.
This approach suffers from the following two limitations:
  • 1. When there is a need to subclass the hierarchy for some other reason, it could lead to an exponential number of subclasses and soon we will have an exploding class hierarchy.
  • 2. Both the abstraction interface and its implementation are closely tied together and hence they cannot be independently varied without affecting each other.
Applying the Bridge pattern, both the interfaces and the implementations of an abstraction can be put into separate class hierarchies.
(figure bridge prototype)
A client application can choose a desired abstraction type from the abstraction class hierarchy. The abstraction object can then be configured with an instance of an appropriate implementer from the Implementer class hierarchy.
This ability to combine abstractions and implementations dynamically can be very useful in terms of extending the functionality without subclassing.
When a client object invokes a method on the Abstraction object, it forwards the call to the Implementer object it contains. The Abstraction object may offer some amount of processing before forwarding the call to the Implementer object.
Example
Take the message logging functionality.A message can be logged to different types of destinations such as a file, console and others.


Depending on the destination type, a different implementation of the logger abstraction is needed. This requirement can be designed with a common Logger interface that declares the interface (methods) of the abstraction and different implementers corresponding to different destination types provide implementation for the logger abstraction.

Let us define two such implementers
  • FileLogger
  • ConsoleLogger
to log messages in plain text format. to a file and console, respectively.
( bridge_logger_example_1 )

Let us suppose that an application object needs to log messages in an encrypted format.
The existing messaging logging functionality design is not sufficient without either:

  • Modifying different implementers (Modify the existing abstraction and the implementation)
  • Extending the entire class hierarchy ( result in an exponential number of subclasses and soon there will be an exploding class hierarchy)
Having to modify the existing code in order to extend the functionality is not advisable and violates the basic object-oriented open-closed principle.
Applying the Bridge pattern the interface and the implementation of the logger abstraction can be arranged into two separate class hierarchies.
Abstraction Implementation Design
  • The FileLogger writes a given message to a log file using a helper FileUtil class.
  • The ConsoleLogger writes a given message on the screen.

Abstraction Interface Design

The interface for the logger abstraction can be designed in the form of a set of classes representing different types of messages that a client object would like to log.

(logger_abstraction_interface)


  • Client objects do not directly use the interface exposed by the abstraction implementer classes.
  • Each abstraction interface class maintains an object reference of the MessageLogger (abstraction implementer) type. Whenever a client object creates an abstraction interface object, it configures the interface object with a MessageLogger object.
  • interface object does any required preprocessing and uses abstraction implementer object to log the message.
  • The preprocessing functionality is meant to be used internally by abstraction interface objects only and it should not be available to client objects.(use private methods to preprocess the message).
Whenever a client needs to log a message:

  1. It creates an instance of an appropriate MessageLogger implementerclass such as FileLogger or ConsoleLogger.
  2. It creates an instance of an appropriate Message implementer class such as TextMessage or EncryptedMessage.
  3. It configures the Message implementer object with the MessageLogger implementer object created in Step 1. This object is maintained inside the Message implementer object.
  4. It calls the log(String) method on the Message implementer object created in Step 2.
  5. The Message implementer object carries out the required processing to transform the incoming message to the desired formatand forwards the transformed message to the MessageLogger implementer object it contains by invoking its logMessage(String) method.



This relationship between classes in the interface and the implementer class hierarchy can be viewed as a Bridge in this case.


(bridge_logger_example)



No comments:

Post a Comment