Week 4 - Structural Patterns: The Decorator and Composite Patterns

Lecture recording here.

Lab recording here.

Introduction

We will look at some more structural patterns, the decorator and composite patterns. The Decorator pattern allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The Composite pattern composes objects into tree structures to represent whole-part hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

Videos

The Decorator PatternDecorator Design Pattern
The Decorator Pattern Explained and Implemented in Java
The Composite PatternComposite Design Pattern
The Composite Pattern Explained and Implemented in Java

Assignment(s)

Assignment 2 - Travel Simulation

The Decorator Design Pattern

The Rationale

The decorator design pattern is used to add new behavior or functionality to an object dynamically without altering its structure. The main rationale behind the decorator pattern is to allow developers to create a flexible and extensible system that can add new features to an object without changing its existing code.

The decorator pattern achieves this by creating a decorator class that wraps the original object and adds new behavior to it. The decorator class implements the same interface as the original object, so the client code can interact with it as if it were the original object. The decorator also has a reference to the original object and can call its methods to execute its behavior.

This design pattern is particularly useful in situations where you have a large number of objects that require different features, but it is not feasible or desirable to create a subclass for each combination of features. The decorator pattern allows you to add new functionality to an object at runtime by creating a decorator that adds the desired behavior.

A good explanation of the decorator pattern can be seen at Decorator, also known as Wrapper.

The UML

Here is the UML diagram for the decorator pattern:
The Decorator Pattern
Here are the components of the decorator design pattern:

  1. Component: (Shape). This is an abstract class that defines the interface for objects that can be decorated.
  2. Concrete Component: (Circle, Rectangle). These concrete classes provide the basic behaviour that can be decorated.
  3. Decorator: (ShapeDecorator). This is an abstract class that also implements the Component interface. It contains a reference to a Component object and delegates all method calls to this object.
  4. Concrete Decorator: (RedShapeDecorator). These concrete classes add functionality to the Component by callings its methods and adding its own behaviour.

Code Example - The Decorator

The following is a simple implementation of the decorator pattern:
C++: Decorator.cpp.
C#: Decorator.cs.
Java: DecoratorMain.java.
Python: Decorator.py.

Common Usage

The following are some common usages of the decorator pattern:

  1. Adding functionality to existing objects without modifying their source code.
  2. Applying multiple levels of functionality to an object at runtime.
  3. Modifying or extending the behavior of an object dynamically.
  4. Creating complex, hierarchical object structures with varying levels of functionality.
  5. Wrapping third-party libraries or APIs to extend their functionality or adapt them to specific needs.
  6. Providing a flexible, extensible architecture for plug-ins or add-ons.
  7. Separating concerns by delegating specific responsibilities to different decorators.

Code Problem - Button Decorator

We have an implementation for drawing a button. We would like to add a feature to this implementation. The feature is a border. The following code implements the decorator design pattern to do so:
Component.h, the component interface,
Button.h, the concrete component,
Decorator.h, the decorator interface,
BorderDecorator.h, the concrete decorator,
ButtonMain.cpp, the client.

Modified Code Problem - Button Wrapper

A class that is weakly related to the decorator pattern is the wrapper. Wrappers are very useful in ensuring reliable code. The following example uses component wrappers to ensure memory is always cleaned up, even if the programmer forgets to delete the allocated memory. This wrapper class takes an interface (component in this case) as an argument and deletes it when its destructor is invoked similar to a smart pointer. The concept of a wrapper class to ensure reliable code can be applied to the creation and deletion of threads, the opening and closing of files, etc...
Component.h, the component interface,
Button.h, the concrete component,
Decorator.h, the decorator interface,
BorderDecorator.h, the concrete decorator,
ComponentWrapper.h, a wrapper class for a component object,
ButtonMain.cpp, the client.

The Composite Design Pattern

The Rationale

The Composite pattern is a structural design pattern that allows you to compose objects into tree structures to represent part-whole hierarchies. The basic idea behind the Composite pattern is to treat individual objects and compositions of objects in a uniform way so that they can be manipulated in the same way. By treating individual objects and compositions of objects in the same way, the client code becomes simpler and more consistent. The client code does not need to know whether it is dealing with a single object or a composition of objects, making it easier to use and less error-prone.

The UML

Here is the UML diagram for the composite pattern:
The Composite Pattern
Here are the components of the composite design pattern:

  1. Component: This is an abstract class or interface that defines the common operations for both Leaf and Composite classes. It also defines the methods for adding and removing child components, as well as accessing them.
  2. Leaf: This is a concrete class that represents a leaf node in the composite structure. It doesn't have any children and performs a specific operation..
  3. Composite: This is a concrete class that represents a composite node in the composite structure. It has one or more child components and performs a group operation that delegates to its children.

Code Example - The Composite Design Pattern

The following is a simple implementation of the composite pattern:
C++: Composite.cpp.
C#: CompositeMain.cs.
Java: CompositeMain.java.
Python: Composite.py.

Common Usage

The composite design pattern is commonly used in the software industry in situations where a group of objects needs to be treated in the same way as a single object. Here are some common use cases:

  1. Representing hierarchical structures: The composite pattern is often used to represent hierarchical structures like trees or menus. The composite pattern allows you to treat individual nodes and the entire structure in a uniform way.
  2. GUIs: The composite pattern is also used in graphical user interfaces (GUIs) to represent the components of a window or a user interface. The composite pattern is used to create complex visual elements by combining simpler elements like buttons and text fields.
  3. File systems: The composite pattern is used to represent file systems. The file system is treated as a tree structure, where directories and files are nodes in the tree. The composite pattern allows you to perform operations on individual files and directories as well as the entire file system.
  4. Graphics and animations: The composite pattern is used to represent graphics and animations. Graphics and animations are often composed of smaller graphics elements, which are treated as a single entity using the composite pattern.

Code Problem - Employment

We have a company with managers and software engineers. Managers manage software engineers but they can also manage other managers. The composite pattern is employed in the following example to show the collection of the relationships between managers and other managers, and managers and software engineers:
Employee.h, an interface class (component),
SWEngineer.h, a concrete class that represents a leaf node,
Manager.h, a composite concrete class that has one or more child components,
Employment.cpp, the main function.

For a more detailed explanation of this problem, see Composite Design Pattern, the Organization.

Code Problem - Enhanced Employment

We can enhance the above code by adding the hardware engineer as another concrete class that represents a leaf node, and by taking advantage of the add and remove employee functions, in case the company undergoes re-organization. We can also add a display employee function that takes an index into the employee vector:
Employee.h, an interface class (component),
SWEngineer.h, a concrete class that represents a leaf node,
HWEngineer.h, a concrete class that represents a leaf node,
Manager.h, a composite concrete class that has one or more child components,
Employment.cpp, the main function.

Workshop Design Problems

Design Problem: Software Workshop Agenda Builder

You're building an Agenda Builder for a two-day software workshop. The agenda consists of Tracks, which contain Modules, which contain Lessons. Tracks and Modules can also contain other Modules (nesting). The platform must let organizers:

Domain Rules

Constraints & Acceptance Criteria

Example Structure

See a potential solution here.

Design Problem: Workshop Access Pass Customizer

You're building an e-commerce configurator for a software workshop's Access Pass. Each attendee must purchase at least a Base Pass, and can optionally add a variety of premium enhancements. Organizers want to combine these enhancements in any order without creating a separate class or pricing rule for every combination.

What the Platform Must Do

Constraints & Acceptance Criteria

Example User Journeys

See a potential solution here.

Design Problem: Workshop Feedback Analyzer

After a multi-day software workshop, organizers collect large volumes of feedback from participants. This feedback must be analyzed using different analysis methods and reported through different channels.

What the System Must Do

Constraints & Acceptance Criteria

Examples

See a potential solution here.