Template Method is similar to the Strategy pattern but is used for sharing code between algorithms. It defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. This allows subclasses to redefine certain steps of an algorithm without changing the algorithm’s structure.
The class diagram illustrates the structure of the Template Method pattern, showing how an abstract class defines the overall structure while concrete classes implement the primitive operations.
Variations:
Variable operations can be hooks (empty-bodied operations that may be overridden) or abstract operations (no bodies, must be overridden). Hooks provide extension points, while abstract operations enforce specific behavior in subclasses.
The templateMethod
should be declared as final to prevent subclasses from changing the algorithm's structure.
primitiveOperations
should be protected to allow access from subclasses but not from unrelated classes.
Minimize the number of primitive operations to maintain clarity and reduce complexity.
If a step is implemented in a separate class, it resembles the Strategy pattern, but the intent is different. Template Method focuses on algorithm structure, while Strategy focuses on interchangeable algorithms.
Hollywood Principle:
Exemplified by the Template Method: “Don’t call us, we’ll call you.” This means the parent class controls the algorithm's execution flow, and subclasses provide specific implementations when needed.
Children don’t call parents; parents call children. The template method in the parent class invokes operations in the child classes.
Often used by software frameworks like Application in JavaFX, where the user writes ConcreteClass. The framework calls the user-defined code at specific points.
A software framework is a set of classes that forms a complete program and dictates the flow of control, unlike a library, which provides specific functionality but doesn't dictate the overall program structure.
The typical practice is for children to call parents using super
, but this is not enforced by the framework, except when writing constructors in Java.
Iterator is like Factory Method but used for creating an iterator, a pointer into a data structure, used behind the scenes by the enhanced for loop. It provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Enhanced For Loop Example:
Collection<String> strings;
for (String s: strings) {
System.out.println(s);
}
Behind-the-scenes Implementation using Iterator:
Collection<String> strings;
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
System.out.println(s);
}
Iterator Pattern in the Collections API:
Iterator<E>
interface:
boolean hasNext();
: Returns true if the iteration has more elements.
E next();
: Returns the next element in the iteration.
void remove();
: Removes the last element returned by the iterator from the underlying collection (optional). This operation removes the last element returned by the iterator from the underlying collection.
Collection<E>
interface:
Iterator<E> iterator();
: Returns an iterator over the elements in this collection. This method creates and returns an iterator object that can be used to traverse the collection.
The Collections API almost conforms to the Iterator pattern with Aggregate = Collection<E>
and ConcreteAggregate = ArrayList<E>
. However, operations need to be adjusted to match. The main discrepancy is that the remove
operation is optional and may throw an UnsupportedOperationException
.
Iterator also almost conforms to the Factory Method pattern. The iterator()
method in Collection
is akin to a factory method that produces Iterator
objects.
Variations on the Iterator Pattern:
The client could advance the iterator instead of automatic advancement with each call to next()
for more control. This is known as an external iterator.
Additional operations can be added, such as previous
or skipTo
. These enhance the iterator's functionality, allowing for more flexible traversal.
remove
is generally difficult to implement; often an UnsupportedOperationException
is thrown. Implementing remove
requires careful management of the underlying data structure.
Implementation and Consequences:
ConcreteIterator
has a field to track the current position (e.g., int index
for an array or ArrayList). This field is essential for maintaining the state of the iteration.
Two tricky problems:
Thread safety: What if the collection is changed during traversal? Concurrent modifications can lead to unexpected behavior or exceptions.
Making an iterator for Composites is hard (managing head node, current child node, and iterator for that child node). Iterating through composite structures requires a recursive or stack-based approach.
The Iterator pattern is versatile, allowing traversal of different ConcreteAggregates
with multiple traversal strategies (e.g., backward/forward, pre-/in-/post-order), each as a ConcreteIterator
object. This enables clients to traverse data structures in various ways without modifying the structures themselves.
Composite is the correct way to form tree structures. It allows clients to treat individual objects and compositions of objects uniformly. This pattern is useful for representing part-whole hierarchies.
Procedural Approach to Trees:
Violates OO design principles. Procedural approaches often lead to code that is difficult to maintain and extend, as operations are tightly coupled to the structure of the data.
Mr. O-O’s Criticisms of the Procedural Approach:
Adding a new type of leaf or internal node requires changing every operation and retesting, violating the Open-Closed Principle. The Open-Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
Each type of node should have a different class inheriting from a general class, utilizing polymorphism. Polymorphism allows objects of different classes to be treated as objects of a common type.
This approach simplifies the Client class. By using polymorphism and inheritance, the Client class can interact with the tree structure through a common interface, without needing to know the specific types of nodes.
The Composite pattern is used by Swing, for example, in its component hierarchy.
Variations on the Composite Pattern:
Many different types of leaf and composite can be added. This flexibility allows for the creation of complex and diverse tree structures.
To restrict composites to specific components, checks must be manually added. These checks ensure that the composite contains only valid child components.
Child components may reference their parents (invariant: every child of a composite has the composite as its parent). This parent reference can be useful for traversing the tree upwards or for maintaining the tree structure's integrity.
Operations like add
, remove
, and getChild
cannot be meaningfully defined for the Leaf class.
Options: Include them in the Composite interface with meaningless definitions or narrow the interface and exclude them. Defining these operations in the Leaf class can lead to code that throws exceptions or does nothing, which may not be desirable.
Decorator can be considered a degenerate version of Composite (linked list vs. tree) but serves a different purpose: adding responsibility. While both patterns involve hierarchical structures, Decorator focuses on adding behavior to individual objects, whereas Composite focuses on structuring objects into part-whole hierarchies.
The Lexi text editor from the Gang of Four book uses several design patterns, including Composite for the document structure and Strategy for different formatting algorithms.
Checking that AttackDemo
Conforms to Strategy
There must exist a Context class (Context = Animal).
A Strategy interface (Strategy = AttackBehaviour).
An abstract operation contextInterface
in Context (contextInterface
= performAttack
).
An abstract operation algorithmInterface
in Strategy (algorithmInterface
= attack
).
A set of classes ConcreteStrategy1...ConcreteStrategyn
(ConcreteStrategy1 = SwoopStrategy etc.).
Context aggregates with Strategy.
ConcreteStrategyi
are all concrete, inherit from Strategy.
Context.contextInterface
calls Strategy.algorithmInterface
. This call delegates the behavior to the concrete strategy.
They must all be different
For n ≥ 1; this generalisation from n=3 is implicit
Strategy Conditions Restated in Predicate Logic for Clarity
\exists Context, Strategy, contextInterface, algorithmInterface, ConcreteStrategies \cdot
Context \in classes \land Strategy \in classes \land ConcreteStrategies \subseteq classes \land
contextInterface \in Context.opers \land algorithmInterface \in Strategy.opers \land isInterface(Strategy) \land assoc(Context, Strategy) \land
\forall CS \in ConcreteStrategies \cdot \neg isAbstract(CS) \land inherits(CS, Strategy) \land calls(Context.contextInterface, Strategy.algorithInterface)
To see if the design satisfies the pattern:
Replace the existentially-quantified variables with actual classes/operations from the diagram (e.g., Context=Animal).
Evaluate the condition with the classes/operations.
If the condition is true, the design satisfies the pattern; if not, no.
In the practical, you will do this for the five design patterns of Lexi.
Apply bindings to class variables and sets of class variables- A single subclass usually represents a set of subclasses
Apply bindings to operation variables and sets of operations variables- A single operation often represents a set of operations
Add extra classes and operations, as appropriate
Drawing Stick People
AbstractClass = Person
ConcreteClasses = {Man, Woman}
TempleMethod = draw
PrimitiveOperations = {drawTorso, drawLowerClothes, drawLegs}
Preparing a Drink
AbstractClass = Drink
ConcreteClasses = {Tea, Coffee}
TemplateMethod = prepareRecipe
PrimitiveOperations = {brew, addCondiments}
Template Method: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure. It promotes code reuse and allows variations in specific steps of an algorithm.
Composite: Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly. This simplifies client code and allows for flexible object structures.
Iterator: Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. Iterator simplifies the traversal of collections and allows for different traversal strategies.
Recognized uses of patterns.
Confirmed that a design conforms to a design pattern.
AttackDemo
conforms to the Strategy pattern. This confirmation involves verifying the presence of key elements such as Context, Strategy, and ConcreteStrategies, and ensuring that they interact as defined by the pattern.
Instantiated patterns to particular situations.
Template Method to Stick People. This instantiation involves mapping the abstract and concrete classes, template method, and primitive operations to the specific context of drawing stick figures.
Template Method to Drink Preparation. Similarly, this involves mapping the elements of the Template Method pattern to the process of preparing different types of drinks.
Considered variations on patterns.
Template Method, Iterator, Composite. These variations highlight the flexibility of design patterns and their adaptability to different scenarios.
Compared two similar patterns.
Template Method vs. Strategy. This comparison emphasizes the differences in intent and usage between these two patterns.
Factory Method vs. Iterator. This comparison highlights the different purposes of object creation and collection traversal.
Composite vs. Decorator. This comparison underscores the distinction between structuring objects into hierarchies and adding responsibilities to individual objects.
Considered the consequences of not using particular patterns.
Template Method, Iterator, Composite. Not using these patterns can