Personal tools
You are here: Home Classes Fall 2004 - Spring 2005 CS 151 Design Patterns
Navigation
Log in


Forgot your password?
« May 2008 »
Su Mo Tu We Th Fr Sa
123
456789 10
11121314151617
18192021222324
25262728293031
 
Document Actions

Design Patterns

by admin last modified 2005-05-11 18:12

Design Patterns


Design patterns represent abstract solutions to problems which have been found to arise frequently in software design.  They enable the designer to take the concept of code reuse to a higher level -- the design level -- by reusing a design that has proven to be effective in a variety of applications.  A design pattern generally consists of a group of classes with specific relationships and interactions between them.  We've seen examples of the use of the Observer pattern and the Strategy pattern in our labs.

UML class diagrams are often used to illustrate design patterns.  Some examples:

Observer Pattern

A change in one object will sometimes require other objects to be updated. This relationship can be explicitly coded in the subject, but this requires knowledge about how the observers should be updated. The result is that the objects become tightly coupled and can not easily be reused.

The Observer pattern solves this by creating a loosely bound one-to-many relationship between an (observable) object and others (observers) that depend on it. Whenever an observable object changes state, its observer classes are notified, enabling them to update themselves accordingly.

This pattern is used when there are common classes that encapsulate state, and other classes that represent a "view" of that state. If you make the related classes observers, who look at a common repository of data for their state, you can maintain consistency and no observer knows about the others. For example, in a spreadsheet application, the observers could be the numerical cells, the graphs, and the pie charts. They all look to the data (subject) to determine how they should look. The subject has methods to attach, detach, and update the observers in the event that its state changes.





Strategy Pattern

The Strategy Pattern is used when we want to encapsulate an algorithm, or family of algorithms, inside a class, to hide the implementation details from the objects that use them.

One class is responsible for carrying out work on behalf of another, and is left to its own devices for performing the work.  The client object does not know, or care, how the work is done on its behalf.  The client, then need not contain any methods or variable for supporting the strategy in its execution of the work.  The strategy design pattern is a way of abstracting behavior and capturing that behavior in an object.  It is a good example of separating the invariant and variant behaviours of a system.  It is done by breaking a design into two parts:
  1. The Context part of the design, which deals with the behavior at an abstract level. The behavior is thus referred to as the strategy for accomplishing a task. The context is the invariant part of the design in that its behaviors do not change from situation to situation, and so it can be encapsulated into a single object.
  2. The Strategy part of the design captures the variant nature of the design.  The particular actions taken when accomplishing a task change from situation to situation, even dynamically as the program is run. The strategy section consists of an abstract strategy class and a series of concrete strategy subclasses. Each subclass represents specific actions that could be taken when the strategy is executed by the context.




Visitor Pattern

The Visitor pattern is designed to allow distinct, unrelated operations to be performed on node or member objects within an aggregate (e.g. trees, lists) structure. Using a Visitor design avoids "polluting" the node classes with these operations. It also avoids querying and casting each node to the correct type before performing the desired operation.

As a result, node classes can be more lightweight, processing functionality is removed from their responsibility, and they can be narrowed down to their functions in the aggregate structure, which is usually informational in purpose.

Instead, each node element implements an 'accept' method, which accepts a visitor object.  The node element then calls the Visitor's processing method, passing itself as an argument.  Each operation to be supported is modelled with a concrete derived class of the Visitor base class.

This defines what is described as a 'double-dispatch'.  When the Client needs an operation to be performed on node objects, it calls the accept() method on each node, using the desired Visitor instance. The accept() method passes (dispatched) control to the appropriate Visitor operation. The accept() dispatch plus the visit() dispatch gives us double dispatch.  Rather than the type of the receiver determining the operation (single-dispatch), the type of the Visitor is also a factor. 

 


We could have used a visitor to implement the evaluate, postfix, prefix, and infix algorithms for the Expression tree in the Calculator lab.  You can take a look at the files below to see how the visitor pattern could have been applied:

ExpressionVisitor.java -- shows how the Visitor interface is defined (corresponding to AVisitor in the UML diagram above)
Expression.java -- an abstract class representing the accepting classes (corresponding to AElement above)
ConstantExpression.java -- a concrete accepting class (corresponding to ConcreteElementA above)
BinaryExpression.java -- another concrete accepting class (corresponding to ConcreteElementB above)
Calculator.java -- contains a concrete implemenation of ExpressionVisitor, written as an anonymous class (the anonymous class corresponds to ConcreteVisitor1 above)

Try writing a visitor to convert an Expression to its representation as a prefix string.




 

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: