TP

Design Patterns Lecture Notes

Template Method Pattern

  • 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 Pattern

  • 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:

    1. Thread safety: What if the collection is changed during traversal? Concurrent modifications can lead to unexpected behavior or exceptions.

    2. 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 Pattern

  • 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.

Case Study: Lexi Text Editor

  • 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.

How to Check That a Design Conforms to a Design Pattern

  • Checking that AttackDemo Conforms to Strategy

    1. There must exist a Context class (Context = Animal).

    2. A Strategy interface (Strategy = AttackBehaviour).

    3. An abstract operation contextInterface in Context (contextInterface = performAttack).

    4. An abstract operation algorithmInterface in Strategy (algorithmInterface = attack).

    5. A set of classes ConcreteStrategy1...ConcreteStrategyn (ConcreteStrategy1 = SwoopStrategy etc.).

    6. Context aggregates with Strategy.

    7. ConcreteStrategyi are all concrete, inherit from Strategy.

    8. Context.contextInterface calls Strategy.algorithmInterface. This call delegates the behavior to the concrete strategy.

    9. 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

Instantiating Class Diagrams for Patterns

  1. Drawing Stick People

    • AbstractClass = Person

    • ConcreteClasses = {Man, Woman}

    • TempleMethod = draw

    • PrimitiveOperations = {drawTorso, drawLowerClothes, drawLegs}

  1. Preparing a Drink

    • AbstractClass = Drink

    • ConcreteClasses = {Tea, Coffee}

    • TemplateMethod = prepareRecipe

    • PrimitiveOperations = {brew, addCondiments}

Pattern Intents

  • 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.

Conclusion and Summary

  • 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