Skip to content

Week 15 - Compound Design Patterns

Compound Design Pattern

Classification

  • Compound: Multiple design patterns working together to solve a common problem.

Pattern Definition

  • Definition: Multiple smaller design patterns working together to solve a common problem. It is only a compound design pattern if it has been used to solve multiple problems, not just because it uses multiple design patterns.

Representations

  • MVC is a Combination of:
    • Composite: View -> UI Composite Components (e.g. JavaFX Layouts) and UI Leaf Components (e.g. JavaFX button, labels, etc.)
    • Strategy: View -> Controller
    • Observer: Model -> Controller or View

Strategy Review

The Strategy design pattern is a behavioral pattern that enables selecting an algorithm’s behavior at runtime. It defines a family of algorithms, encapsulates each one in a class, and makes them interchangeable. This pattern allows the algorithm to vary independently from the clients that use it.

Key Components
  • Strategy Interface: Defines a common interface for all supported algorithms. Each concrete strategy implements this interface.
  • Concrete Strategies: Encapsulates and implements the algorithm using the Strategy interface.
  • Context: Composes a Strategy object and delegates calling strategy algorithm defined by a ConcreteStrategy.
Intent
  • Encapsulate Algorithms: Encapsulate a family of algorithms, making them interchangeable.
  • Runtime Selection: Allows the client to choose the appropriate algorithm at runtime.
  • Separation of Concerns: Separate the algorithm’s implementation from the client that uses it, promoting flexibility and reusability.

The Strategy pattern allows you to easily switch between different algorithms or behaviors without altering the client code that uses them. Thus, making the system more flexible and easier to maintain.

UML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
classDiagram
    class Context {
        - Strategy strategy
        + setStrategy(Strategy strategy) void
        + doSomething(data) void
    }

    class Strategy {
        <<interface>>
        + execute(data) void
    }

    class ConcreteStrategyA {
        + execute(data) void
    }

    class ConcreteStrategyB {
        + execute(data) void
    }

    class Client {
        + createStrategies() void
        + executeContext() void
    }

    Context *--> Strategy : delegates execute()
    Strategy <|-- ConcreteStrategyA
    Strategy <|-- ConcreteStrategyB
    Client --> Context : creates and sets strategy
    Client ..> ConcreteStrategyA : creates
    Client ..> ConcreteStrategyB : creates

Composite Review

The Composite design pattern is a structural pattern that allows you to treat individual objects and compositions of objects uniformly. It creates an upside-down tree structure of leaf and composite objects, enabling clients to interact with both types of objects in the same way.

Key Components
  • Component: Defines the default behavior for the interface for all objects in the composition. It declares the interface for objects in the composition and (optionally) defines an interface for accessing a component’s parent in the recursive structure, allowing it to be managed uniformly.
  • Leaf: Represents the end objects of the composition. A leaf has no children and defines behavior for primitive objects in the composition.
  • Composite: Defines behavior for components having children and implements child-related operations in the Component interface. It stores child components and implements operations that involve traversing or manipulating its children.
  • Client: Manipulates objects in the composition through the Component interface.
Intent
  • Uniform Interface: Provide a uniform interface for individual objects and compositions of objects.
  • Tree Structure: Create a tree structure of objects where individual objects and compositions of objects are treated uniformly.
  • Simplify Client Code: Allow clients to treat individual objects and compositions of objects consistently, simplifying the client code.

The Composite pattern allows you to build complex hierarchies of objects while keeping the client code simple and flexible. This pattern is particularly useful when dealing with hierarchical/trees or recursive data structures, such as file systems, GUIs, and org-charts.

UML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
classDiagram
    class Component {
        <<interface>>
        + execute(): void
    }

    class Leaf {
        + execute(): void
    }

    class Composite {
        - List<Component> children
        + execute(): void : "for each child c c.execute()"
        + add(Component component): void
        + remove(Component component): void
        + getChildren(): List<Component>
    }

    class Client {
        + useComposite(): void
    }

    Component <|-- Leaf : implements
    Component <|-- Composite : implements
    Composite o--> Component : contains
    Client --> Component : creates

Observer Review

The Observer design pattern is a behavioral pattern that defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. This pattern is also known as the Publish-Subscribe pattern.

Key Components
  • Subject (AKA Observable): Knows its observers and provides an interface for attaching and detaching Observer objects. It maintains a list of its observers and notifies them of any state changes.
  • Observer: Defines an updating interface for objects that should be notified of changes in a subject. It provides an update method that is called when the Subject’s state changes.
  • ConcreteSubject: Stores the state of interest to ConcreteObserver and sends a notification to its observers when its state changes.
  • ConcreteObserver: Maintains a reference to a ConcreteSubject object, stores state that should stay consistent with the Subject’s, and implements the Observer updating interface to keep its state consistent with the Subject’s.
Intent
  • Decouple Subject and Observers: Establish a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
  • Dynamic Dependencies: Allow an object (i.e. Subject) to dynamically maintain a list of Observers that should be notified of any state changes.
  • Loose Coupling: Promote loose coupling between the Subject and its Observers, making the system more flexible and easier to maintain.

With the Observer pattern, you can create a system where objects can dynamically register to be notified of changes in other objects, enabling a more modular and decoupled design. This pattern is commonly used in event handling systems, model-view-controller (MVC) architectures, and other scenarios where objects need to stay synchronized with each other.

UML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
classDiagram
    class Subject {
        <<interface>>
        + register(Observer observer): void
        + unregister(Observer observer): void
        + notify(): void
        + getState(): Object
        + setState(Object state): void
    }

    class Observer {
        <<interface>>
        + update(): void
        + setSubject(Subject subject): void
    }

    class ConcreteSubject {
        - List~Observer~ observers
        - Object state
        + register(Observer observer): void
        + unregister(Observer observer): void
        + notify(): void "for each Observer o o.update()"
        + getState(): Object
        + setState(Object state): void
    }

    class ConcreteObserverA {
        - Subject subject
        + update(): void "subject.getState(); doSomething()"
        + setSubject(Subject subject): void
    }

    class ConcreteObserverB {
        - Subject subject
        + update(): void
        + setSubject(Subject subject): void
    }

    class Client {
        - Subject subject
                + main(): void "o = new ConcreteObserverA();subject.register(o)"
    }

    Subject <|-- ConcreteSubject
    Observer <|-- ConcreteObserverA
    Observer <|-- ConcreteObserverB
    ConcreteSubject o--> Observer : maintains list
    Client --> Subject : creates
    Client ..> Observer : create and registers

Bringing it all Together

  • Observer Pattern
    • Subject Interface: Represents the Model in MVC.
    • ConcreteSubject: A specific Model implementation.
    • Observer Interface: Represents the Views in MVC.
    • ConcreteObserver: Specific Views that respond to changes in the Model.
  • Strategy Pattern
    • ViewStrategy Interface: Defines how Views are rendered.
    • Concrete Strategies: Implement rendering logic (e.g., HTML, JSON).
  • Composite Pattern
    • Component Interface: Represents a generic View element.
    • Leaf: Simple View components (e.g., a text field or button).
    • Composite: Complex Views containing nested components.
  • Client: Routes requests, invokes the appropriate Controller, selects a ViewStrategy, and triggers the rendering pipeline.

Mermaid Graph - MVC Pattern

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
classDiagram
    class Subject {
        <<interface>> ObserverPattern
        + register(Observer observer): void
        + unregister(Observer observer): void
        + notifyObservers(): void
        + getState(): Object
        + setState(Object state): void
    }

    class ConcreteSubject {
        <<ObserverPattern>>
        - List~Observer~ observers
        - Object state
        + register(Observer observer): void
        + unregister(Observer observer): void
        + notifyObservers(): void
        + getState(): Object
        + setState(Object state): void
    }

    class Observer {
        <<interface>> ObserverPattern
        + update(): void
        + setSubject(Subject subject): void
    }

    class ConcreteObserver {
        <<ObserverPattern>>
        - Subject subject
        - Component viewRoot
        + update(): void
        + setSubject(Subject subject): void
        + renderView(): void
    }

    class Component {
        <<interface>> CompositePattern
        + render(ViewStrategy strategy, HttpResponse response): void
    }

    class Leaf {
        <<CompositePattern>>
        + render(ViewStrategy strategy, HttpResponse response): void
    }

    class Composite {
        <<CompositePattern>>
        - List~Component~ children
        + add(Component component): void
        + remove(Component component): void
        + getChildren(): List~Component~
        + render(ViewStrategy strategy, HttpResponse response): void
    }

    class ViewStrategy {
        <<interface>> StrategyPattern
        + renderLeaf(String content, HttpResponse response): void
        + renderCompositeStart(HttpResponse response): void
        + renderCompositeEnd(HttpResponse response): void
    }

    class HTMLViewStrategy {
        <<StrategyPattern>>
        + renderLeaf(String content, HttpResponse response): void
        + renderCompositeStart(HttpResponse response): void
        + renderCompositeEnd(HttpResponse response): void
    }

    class JSONViewStrategy {
        <<StrategyPattern>>
        + renderLeaf(String content, HttpResponse response): void
        + renderCompositeStart(HttpResponse response): void
        + renderCompositeEnd(HttpResponse response): void
    }

    class Router {
        + routeRequest(HttpRequest request, HttpResponse response): void
    }

    class Controller {
        + setViewStrategy(ViewStrategy strategy): void
        + renderView(Component view, HttpResponse response): void
    }

    Subject <|-- ConcreteSubject
    Observer <|-- ConcreteObserver
    Composite o-- Component : "children"
    Component <|-- Composite
    Component <|-- Leaf
    ViewStrategy <|-- HTMLViewStrategy
    ViewStrategy <|-- JSONViewStrategy
    ConcreteObserver --> Component : "viewRoot"

MVC Usage

  • View object is responsible for user input and output/presentation.
  • The controller object implements the logic for the allowable transactions that can be performed on the model.
  • The model object encapsulates the fine-grained business logic and data then notifies view/controller of updates.

MVC Sequence Diagram

1
2
3
4
5
6
7
8
sequenceDiagram
    Web User->>+View: 1. Need some information
    View->>+Controller: 2. Handle Event/User Request
    Controller->>+Model: 3. Query Information
    Model-->>-Controller: 4. Result Set/Model Data
    Controller->>+Controller: 5. Validation
    Controller-->>-View: 6. Update the View
    View-->>-Web User: 7. Notify User

Real World Usages

  • ASP.Net MVC
  • Django
  • Magento
  • Spring

Java Code Example

Main Take-Aways from the textbook

  • The Model View Controller (MVC) Pattern is a compound pattern consisting of the Observer, Strategy, and Composite Patterns.
  • The model makes use of the Observer Pattern so that it can keep observers updated yet stay decoupled from them.
  • The controller is the Strategy for the view. The view can use different implementations of the controller to get different behavior.
  • The view uses the Composite Pattern to implement the user interface, which usually consists of nested components like panels, frames, and buttons.
  • These patterns work together to decouple the three players in the MVC model, which keeps designs clear and flexible.
  • The Adapter Pattern can be used to adapt a new model to an existing view and controller.
  • MVC has been adapted to the web.
  • There are many web MVC frameworks with various adaptations of the MVC pattern to fit the client/server application structure.

Main OOP Principles of State Pattern

  • All principles that apply to Strategy, Composite, and Observer Design Patterns.
  • Loosely Coupled: Separation of concerns, the controller decouples the view from the model and vice versa.