CSC/ECE 517 Fall 2010/ch7 7d EC

From Expertiza_Wiki
Jump to navigation Jump to search

Introduction to Design Patterns

A design pattern is a type of technique that developers use to create good software design. There are many design patterns available, and they share common benefits. The benefits of design patterns include [1] :

     1. Code is more accessible and understandable.
     2. Use of common vocabulary to help collaboration among developers.
     3. Helps people understand the system more quickly.
     4. Easier to modify system.

GRASP

GRASP stands for General Responsibility Assignment Software Patterns and consists of several design patterns for assigning responsibility and relationships between classes and objects in object-oriented design.

Some of the patterns in GRASP include Information Expert, Creator, Controller, Low Coupling, High Cohesion, Polymorphism, Pure Fabrication, Indirection, and Protected Variations [2].

In this article, we will focus on the Indirection Pattern.


The Indirection Pattern

The Indirection Pattern supports low coupling and code reuse by using a mediator object in between two objects. Low coupling means that components such as the client and provider as shown below do not or have very little direct interaction with each other. For example, the client object would not call direclty a method in the provider object, and vise versa. Because of this, we can reuse the provider or client classes, since they are not direclty tied in with each other.

A very simple diagram below illustrates the relationship between objects in the indirection pattern.


The relationship between classes in indirection pattern.

In the indirection pattern, a mediator class acts as a proxy in between the client and provider classes. The client interfaces with the mediator, not to the provider. The client accesses functionality from the provider through the mediator. Therefore, the relationship between the client and provider is indirect.

The indirection pattern is seen all of the time. Anytime the programmer accesses an API, be it for operating system call, graphics library call, the programmer is interfacing with a mediator that provides functionality on behalf of the client to some lower-level hardware functionality provided by the operating system or graphics library, etc.

A simple example of the indirection pattern is illustrated below.


Example of Indirection Pattern

Let's say we have a talking robot, and the functionality to get the robot to say a specified string has already been implemented in the code below.

class RobotController {
     void say(String s) {
       // Some complex code for robot to say 's'
     }
}



We want to create another class that has methods to say common phrases like “hello” and “goodbye”.

class RobotPhrases { 

     RobotController r = new RobotController();
     
     void sayHello() { 
       r.say(“Hello”); 
     } 
     
     void sayGoodbye() {
	 r.say(“Goodbye”);
     }
}



In the main function, we would do the following:

public class Main {
     public static void main(String[] args) {
         RobotPhrases rp = new RobotPhrases();
         rp.sayHello();
	 rp.sayGoodbye();
     }
}




The RobotPhrases object instantiates a RobotController object to act as a mediator to access functionality in RobotController.

From the main class, it appears that our new RobotPhrases class has implemented the functionality to make the robot speak the specific words, when in fact, we have used a mediator object to do the work. Basically, we have passed of the duty to someone/something else [3].

Specifically, the mediator object is the RobotController r object created in the RobotPhrases that mediates the action between the two classes.

The advantage of the indirection pattern in this case, is that the RobotPhrases class is much easier to implement since we did not have to put complex, low-level robot code in the class. Also, when the speaking functionality of the robot changes, we only need to modify the say function in RobotController and not in the sayHello and sayGoodbye functions in the new class. This makes maintainability and code reuse much greater.


Another Example: Ruby on Rails

Another example of where the indirection pattern is used is the system design architecture used for web development with Ruby on Rails.

Rails uses the MVC architecture, where applications are separated into three components: model, view, and controller [4].

To briefly describe the tasks of the components [4]:

Model

Responsible for maintaining state of application. Includes data storage, generally in the form a database.

View

Provides the user interface for input and output.

Controller

Orchestrates input from view to model and provide output to view.


The MVC architecture used in Rails

Let's assume that we implement an online music store using Rails and the MVC architecture. A typical use case is illustrated below.

     1. User enters http://my.url.com and view is displayed.
     2. User enters “beatles” in search box.
     3. Search parameter is passed to controller.
     4. Controller accesses songs database in Model to search for query.
     5. Model returns result to Controller.
     6. Controller creates view to display results.


From this use case, the separation between view and model is illustrated. The Model does not directly interact with the View, and the View does not directly interact with the Model. The Controller acts as a mediator between View and Model.

The advantage of this approach is extensibility and maintainability. It is easier for the programmers to develop when components are divided, rather than trying to code everything in one monolithic program. When components are divided there is less coupling between components, and it makes it easier to change or add functionality.


Indirection through Delegation

Indirection can also be achieved through the use of delegation.

To see this, lets assume that we are building a robot and need to implement robot functions. An existing class called RobotController already exists and has the functions we desire and then some that we don't want.


UML diagram for existing RobotController class

class RobotController {     
     void say(String s) { 
       // Some complex code for robot to say 's' 
     } 
     
     void walk() {
	 // Some complex code to walk
     }

     void grab() {
       // Some complex code to grab nearest item	
     }

     void selfDestruct()
       // Code to self destruct robot
     }

     void fireWeapon()
       // Code to fire weapon
     }
 }



The RobotController class contains all of the functionality that we desire, except we don't want selfDestruct() nor fireWeapon() methods!

One solution would be to copy over this class to create a new class, and just delete the unwanted methods. However, this involves copying over a lot of complex code, and if that code were to change, we would need to change it in the new class as well.

Another solution would be to extend this class and just override the unwanted methods with error message or simply just do nothing and return. For example, the following implementation could be done.

class SafeRobotController extends RobotController {     
     void selfDestruct()
       // Don't want this, so just return
	 return;
     }

     void fireWeapon()
       // Don't want this, so just return
	 return;
     }
 }




Extending and overriding unsafe methods in RobotController

This seems like an elegant solution, however, what happens when someone adds more functionality to RobotController such as smashObject() or becomeSelfAware(). Unfortunately, the SafeRobotController will inherit these methods.


Will SafeRobotContoller still be “safe” when the RobotController class changes to the above?

Another solution is to use indirection and not directly involve the RobotController class. Using delegation, we can isolate the class and selectively implement the desired functionality. The code below illustrates how this can be achieved.

class SafeRobotController {     

     private RobotController rc;

     public SafeRobotController {
	rc = new RobotController();
     }

     void say(String s) { 
	rc.say(s);
     } 
     
     void walk() {
	rc.walk();
     }

     void grab() {
	rc.grab();
     }
 }



Here, the SafeRobotController instantiates a RobotController object and calls only the desired methods. Even if some evil programmer adds ill methods to RobotController, they cannot be accessed by SafeRobotController.

Essentially, we have added indirection between SafeRobotController and RobotController through the use of a mediator object.


Using Indirection to Mediate Several Objects

Indirection can also be used to centralize the communication between several related objects [7].

Imagine if we had to make this robot walk. We call LeftFoot to step forward, as well as LeftArm to move up, then LefttArm sends message to RightFoot to step up, which calls RightArm to move up, which calls LeftFoot to step forward, and repeat the process.

The objects would be calling each other through a series a direct calls and this requires lots of coordination between objects.


Trying to make robot walk like this?

Also, when we add another component, say Head, we would have to change all other component objects in order add another component.

One way to relieve each class of the burden of having to know which message to pass to the next object, is the use of mediation.

By using a mediator, in this case, called Controller, we can implement the walking procedure in the controller object and remove the direct calling between component objects.


Getting robot to walk using mediator

The Controller object well now call the components, instead of components calling each other. This relieves each component from the burden of having to know how to interact with each other component. The code becomes much less, and less confusing.

Also, when we add additional components, we just change the code in Controller.


Advantages of Indirection

     1. Reduces coupling between objects.
     2. Code can be reused.
     3. Code is more extensible and easier to maintain.
     4. Code is more accessible and understandable.
     5. Reduces the amount of communications between objects.
     6. Potentially more secure. Delegation can restrict access to unwanted functionality.


Disadvantages of Indirection

     1. Mediator object itself could become too complex.
     2. Potential performance issues if mediator is poorly implemented or too complex.



Summary

The indirection pattern is a powerful design pattern that helps the software designer implement good design by maximizing code reuse and extensibility. In the examples shown above, indirection reduces the coupling between classes or components that are likely to change [6]. By doing so, one could reuse or extend one component without having to affect the other.


Sources

[1] Dale Skrien. Object-Oriented Design Using Java. pp. 196-197
[2] Wikipedia - GRASP
[3] Wikipedia - Delegation Pattern
[4] Dave Thomas. Agile Web Development with Rails. pp. 11-14
[5] Dr. Gehringer. Lecture notes 10. pp 12-13
[6] More GRASP Patterns
[7] Head First Design Patters. Eric Freeman & Elisabeth Freeman. pp. 622-623