CIS-75 Home http://www.c-jump.com/CIS75/CIS75syllabus.htm
Creational | Structural | Behavioral |
---|---|---|
|
The application should be less dependent on how the objects are created...
...The creational patterns abstract the process of object instantiation.
Common goals of Creational Patterns are:
to encapsulate knowledge about concrete classes that are instantiated
to hide low-level details of creation algorithms from the client
to return class interface back to the client (not a reference to the concrete class.)
to manage concrete object lifecycle.
Families of classes are typical in many designs:
Before any of these objects can be used, they must be created, i.e. instantiated.
Problem to solve: client wants to create objects without specifying subclass types.
Abstract Factory ( download ) is a design pattern for creating families of related objects.
Client must use an abstract interface, known as abstract factory to manufacture instances of object supply.
Client must also use an abstract interface to access object supply.
Abstract factory relies on plugin-style concrete suppliers (concrete factories.)
For example, BombedMazeFactory derives from MazeFactory and re-defines BombedMazeFactory::MakeWall( ) operation:
Wall* MazeFactory::MakeWall(); Wall* BombedMazeFactory::MakeWall();
|
Each room maintains a list of four MapSite references to Walls and Doors, each representing a side of the room.
The Door can be open or closed.
Each room has a number.
The Abstract Factory ( download ) sample illustrates runtime "call dispatch" mechanism for
Wall* MazeFactory::MakeWall(); Wall* BombedMazeFactory::MakeWall();
In summary,
MazeFactory::MakeWall( ) is an abstract operation (that is, virtual function in C++.)
MazeFactory and BombedMazeFactory are two concrete suppliers of
Wall
BombedWall
To differentiate between graphical user interface toolkits such as
we may consider the following abstract factory-based design:
Without a cost of change or disruption to the client software:
Additional concrete suppliers can be introduced or removed.
Additional kinds of object supply can also be introduced or removed.
Related Patterns:
Abstract Factory implementation could use a Factory Method ( download )
Abstract Factory could have a Prototype ( download ) design.
Problem to solve:
Client has a set of configurations for complex objects.
Client wants a uniform way to construct complex objects for each configuration.
|
|
The CountingMazeBuilder analyzes how many Rooms and Doors would be built if maze was constructed by the StandardMazeBuilder.
|
|
|
|
Problem to solve: client wants to create objects without specifying subclass types.
Sample program FactoryMethod.cpp ( download ) illustrates the Factory Method design for instantiation of the family of derived objects:
|
|
Problem to solve:
Client wants to create objects without specifying subclass types.
Client has access to prototypical instances, and wants to create new objects by copying them from existing prototypes.
Solution: Abstract base class specifies a pure virtual clone( ) method.
|
|
|
Problems to solve:
Only one instance of an object is needed throughout the lifetime of an application.
Single instance to be created at the time of first access.
Singletons often control access to resources such as
Database connections (e.g. single license for one connection to the database.)
Network socket connections (e.g. restricting client app to only one connection at any time.)
Clients need global point of access to the Singleton's instance.
|
|
If instance of the class does not exist, the new instance is created.
If an instance already exists, Singleton simply returns a reference to that object.
(Singleton constructor must be protected or private to make sure that the object cannot be instantiated in any other way.)
The singleton pattern must be carefully constructed in multi-threaded applications:
If two threads execute the creation method at the same time. When a singleton instance does not yet exist, both threads must
check if instance exists
only one of the threads should create the new instance.
In concurrent processing, the Singleton::GetInstance( ) should be implemented as a mutually exclusive operation.
The Singleton ( download ) sample illustrates
Singleton,
Lock, and
static operation Singleton::GetInstance( ).
Many patterns can be implemented using the Singleton pattern:
Object Pool Design Pattern ( handout )
Structural Patterns identify relationships between classes with an idea of
decoupling abstractions from their implementations
clarifying the division between interfaces
providing best way to organize and share application memory and resources.
Problem to solve:
We need an interface of an abstract class Base.
We already have another class named Done that implements the needed behavior.
Unfortunately, class Done has the wrong interface, incompatible with Base.
Solution:
Derive new class named Adapter from the Base.
Add Done object as a member to the Adapter class.
Use thin layer of code around Done object to implement the desired Base interface.
The Adapter hides the adaptee's wrong interface from the client:
Clients call operations on an Adapter instance.
In turn, the adapter calls Adaptee operations that carry out the requests.
The Adapter ( download ) sample illustrates the Adapter Pattern.
Class Stack is an adapter of a singly-linked list constructed from the series of Node objects.
Consider adapters when interfaces are quite similar.
class Adapter : public Base { public: // Base functions //... private: Done m_done; };//class Adapter
Or possibly:
class Adapter : public Base, private Done
Or build a two-way adapter, using multiple inheritance where both bases are public:
class Adapter : public Base, public Done
Abstract adapter adds convenience of Base interface to an existing Done implementation:
class Adapter : public Base { public: Adapter( Done* dp ); // Base functions //... private: Done* m_done; };//class Adapter
Problem to solve:
Application object needs an interface to another object.
However, concrete object that implements the interface is not known at design time...
...because the type of concrete object depends on excution environment, operating system type, hardware present, etc., etc.
Solution:
Decouple an abstraction (the interface) from its implementation, so that both can vary independently.
|
|
Problem to solve:
When dealing with tree-structured data, programmers often have to discriminate between a leaf-node and a branch.
This makes code more complex, and therefore, error prone.
Solution:
The solution is an interface that allows treating complex and primitive objects uniformly.
The objects are composed into tree structures to represent part-whole hierarchies.
Composite lets clients treat both individual objects as well as object compositions uniformly.
|
|
Sample program Composite.cpp ( download ) demonstrates complex drawing composed of Shape objects.
The Shapes are organized into a tree using ShapeComposite::add( ) operation.
The program uses uniform interface, namely print( ) operation, to traverse the entire tree, starting from the root node.
The Composite pattern makes the client simple:
Clients use the Component class interface to interact with objects in the composite structure.
If call is made to a Leaf, the request is handled directly.
If call is to a Composite, it forwards the request to its child components.
Disadvantage:
Once tree structure is defined, the composite design makes the tree overly general.
In specific cases, it is difficult to restrict the components of the tree to only particular types.
Therefore, to enforce such constraint, the program must rely on run-time checks, since it cannot use the type system the of programming language.
Problem to solve:
|
|
The Decorator ( download ) sample illustrates dynamic scroll bar solution based on decorator-style composition of objects derived from the VisualComponent interface.
|
|
|
|
Decorator changes class responsibilities, not the interface.
Unfortunately, Decorator design introduces many small look-alike objects, which differ only in the way they are interconnected...
...The application quickly becomes difficult to understand and debug!
However, decorators are very useful as wrappers around application calls, providing reliable way of generating debugging information from software components.
Facade design pattern provides a unified interface to a set of interfaces in a subsystem.
Facade defines a higher-level interface that makes the subsystem easier to use:
Problem to solve:
Consider an object representing expensive resource that is impossible to duplicate:
network connection
large object in memory, such as a "result set" of the database query
an open file
How could we emulate "multiple copies" of these complex and unique objects?
Solution:
A proxy class represents indirect reference to the object with concrete services.
(The underlying concrete object is often referred to as a subject.)
The proxy controls indirect access to another object, while exposing the same interface.
Typically,
One instance of the complex object is created
Multiple proxy objects are created
All proxy objects contain a reference to the single original complex object...
...any operations performed on a proxy is forwarded to the original object.
Once all instances of proxies are deleted, the complex object can also be deallocated.
The Proxy pattern introduces a level of indirection to access other objects.
The indirection has many uses, depending on the kind of proxy:
Remote proxy can hide the fact that an object resides in a different address space.
Virtual proxy can perform optimizations such as creating an object on demand.
Both protection proxies and smart references allow additional housekeeping tasks when an object is accessed.
|
|
Proxies act as contracts for accessing services.
Proxy may have an expiration date, or count decremented with each usage.
Expired proxy (contract) terminates access to the underlying service.
Measuring proxy keeps usage statistics of the service and its traffic.
Remote proxy, while using the same interface, hides complex protocol details of accessing remote service from the client.
Chained proxies represent a combination of contracts, known as contract conjunction.
Proxies can be issued by the resource manager overseeing the pool of proxies with expiration parameters.
In summary,
An Adapter change the interface of an existing object.
Bridge has a structure similar to an object adapter, but a different intent.
Bridge separates an interface from its implementation.
Bridge makes possible for the interface and its implementation to vary easily and independently.
Decorator enhances object without changing its interface.
A decorator is thus more transparent to the application than an adapter is.
As a consequence, Decorator supports recursive composition, which isn't possible with pure adapters.
Proxy defines a representative (or surrogate) for another object and does not change its interface.
See also: Discussion of Structural Patterns regarding
Similarities between structural patterns
Adapter versus Bridge
Composite versus Decorator versus Proxy
The behavioral design patterns...
...identify typical communication patterns between objects
...clarify assignment of responsibilities between objects
...formalize complex control flow that can be difficult to follow at run-time.
The Command pattern introduces transaction-style approach to
Encapsulate client requests by a set of the corresponding command objects.
Place command objects in a queue.
Support "undo" operation by deleting commands from the queue.
|
|
|
Problem to solve:
Consider a container of elements, or some other type of aggregation.
How could we examine the elements held in the container?
The challenge is that client has no knowledge about
container type (tree, linked list, hash table, array, etc.)
container size
keys of associative containers
The client wants a uniform interface to navigate container elements while applying various algorithms of traversal.
|
|
|
|
Null Iterator is a degenerate iterator that's helpful for handling container boundary conditions.
By definition, a null iterator's IsDone( ) operation always evaluates to true.
Null Iterator makes traversing the aggregates easier:
At any point in the traversal, a Concrete Iterator can be compared to the Null Iterator;
If the result is true, the traversal is complete;
If the result is false, the traversal should continue.
An Iterator object encapsulates the internal structure of how the iteration occurs.
Iterators are common in object-oriented systems.
Most collection class libraries offer iterator-style access to their elements.
The
Problem to solve:
Consider an object of class Item has considerable hidden state information.
The client wants to:
remember checkpoint state
modify the state
optionally restore the state to the checkpoint state.
Minimal impact on the original Item class interface is preferred!
|
|
The Caretaker
implements the "undo" mechanism;
takes responsibility to store Memento objects in memory.
Note: the Caretaker never operates on, or examines the contents of a memento.
|
|
Problem to solve:
Consider an object with state, referred to as a subject.
There may be a number of objects in the system who depend on the subject's state.
Therefore, dependent objects would benefit from becoming observers of the subject's state.
Solution:
Let Observers register with the Subject.
When Subject undergoes its state change, all Observers are notified.
The Observer pattern realizes a framework known as Publish/Subscribe, or Event Listener.
|
|
The observer pattern is often associated with the Model-View-Controller, or MVC paradigm.
In MVC, the observer pattern is used between the model and the view.
Modification in the model triggers notifications sent to the view observers.
For example, Java Swing is issuing change notifications to its views via the PropertyChangeNotification mechanism.
Model classes (Java beans) behave as Subjects.
View classes, associated with visible items, behave as Observers.
When changes are made to the model, the views are updated accordingly.
|
|
ParaWeatherData is the Concrete Subject generating "weather updates".
There are two concrete observers, CurrentConditionBoard and StatisticBoard. Both observers register themselves with the subject.
ObserverBoardInterface is the abstract Observer interface with update operation.
DisplayBoardInterface is the abstract interface with show operation.
|
|
Both events are handled by concrete observers CurrentConditionBoard and StatisticBoard, accordingly.
Problem to solve: consider mixed hierarchy of nodes representing a messy(*) three-dimensional scene in a graphics application.
(*)The nodes represent many unrelated classes, with no common parent class.
How could we uniformly examine the nodes?
We might try an Iterator pattern, which separates the algorithm from a collection of objects.
However,
Iterators traverse objects only in homogeneous containers.
Iterators don't work with elements of unrelated data types.
To invoke operations via an iterator, the objects must derive from a common parent class.
Other iterator and container type-specific restrictions may exist.
One possible solution is to
derive all objects from a common base class
add required operations to existing classes.
However, many distinct and unrelated operations will be performed on the nodes of the three-dimensional scene structure...
...and sometimes it is best to avoid "polluting" existing classes with new operations.
Better solution: use Visitor design pattern, which allows to
traverse objects inside complex structures
keep related operations together by defining them inside the Visitor class.
|
|
Visitor does not have Iterator-specific restrictions.
Visitor can visit objects that don't have a common parent class.
Any type of object can be added to Visitor's interface:
class Visitor { public: void visit( TypeOne* ); void visit( TypeTwo* ); };
where TypeOne and TypeTwo do not have to be related through inheritance at all.
Visitors make adding new operations easy: add another visitor and define its visit() operation accordingly.
|
|
The patterns represent excellent study cases, but...
Sometimes code that looks "well written" will perform poorly in a real application.
A good programmer writes practical code...
...which has nothing to do with memorization of design patterns.
Often a set of false assumptions leads to decisions such as "It looks like we should use a decorator pattern here..."
Don't waste your time searching for patterns in early stages of the design cycle!
Good programming skills should not be diminished by a pattern "guesswork". Find you own design solutions, validate them against well-known patterns.
Writing code outside of "pattern-based framework" is like programming with the textbook closed. It's a good thing!
Be skeptical about "design solutions" presented in this outline.
Although design patterns are useful knowledge, don't let them blind your own vision.
See also:
Wikipedia article about
Online
Check out
...so be careful :-)