Structural Patterns

Overview

Structural patterns define the way of assembling objects and classes into complex structures, keeping these structures flexible and efficient at the same time.

Materials

πŸ”Œ Adapter

Adapter pattern connects objects with incompatible interfaces and allows them to collaborate with each other. The Adapter is a special object that converts the interface of one object to make it usable by other services.

Class Diagram

Adapter Class Diagram

Structure

  • Client contains business logic that uses Service functionality
  • IClient interface that describes how Client works
  • Service is a class that the Client can’t use directly because of incompatible interfaces
  • Adapter is a middleware class that proxies Service methods invocation from the Client and converts input/output parameters to the corresponding interfaces

When to use

  • When there is an existing system or third-party class that does its job well but has an incompatible interface and can’t be used directly from the target module
  • To integrate modern services with a legacy codebase
  • For reusable classes that must be consumed by the future modules whose interfaces are unknown in advance

Implementation

Notes

  • Adapter can implement a required functionality that the Client requests but missing in the adopted class.
  • Adapter is used for already existing classes, while the Bridge is used to build new connections.

Examples

πŸŒ‰ Bridge

Bridge pattern is used to split a large class (or classes) into separate hierarchies (abstractions and implementations) that can be managed independently.

If the object has some additional characteristic, it can be extracted into a separate class hierarchy instead of extending the existing classes. The old and new classes will be connected via a bridge represented by a common interface.

The Bridge pattern consists of abstraction and implementation:

  • Abstraction is a high-level control interface describing the underlying entities.
  • Implementation layer provides a logic that conforms to Abstraction interfaces.

Class Diagram

Bridge Class Diagram

Structure

  • Abstraction provides high-level control logic that relies on the Implementation
  • Implementation declares an interface that is common for all concrete implementations. An Abstraction can communicate with the ConcreteImplementation only over this interface
  • Client links the Abstraction with a ConcreteImplementation and works with its methods
  • RefinedAbstraction (optional) may provide various control logic flows

When to use

  • To divide a monolithic class that has a few variants of some logical functionality
  • To extend a module in several independent dimensions
  • When you need to switch implementations in the runtime
  • When the implementation changes shouldn’t affect the client’s abstraction

Implementation

Examples

πŸͺ† Composite

Composite composes objects into tree structures and allows working with them as if they were individual objects. This pattern makes sense only when the system model has a tree structure.

The idea is that each object in a tree can manage its nested objects via a common interface. Once a method is called on a root object, it is passed down the tree to get the result by examining all the underlying entities.

Class Diagram

Composite Class Diagram

Structure

  • IComponent interface declares the common operations for composable Leafs and Compositors in the tree
  • Leaf is an elemental building block of a system that contains logic and doesn’t have nested elements
  • Compositor is a container class that holds nested Leafs or other containers

When to use

  • When the system is represented by a tree-like structure
  • To produce the same operations on both primitive components and complex containers
  • When the system hierarchy has a recursive composition

Implementation

Notes

  • Usually, Leaf primitive components do the actual work when Composite complex containers delegate this work to their sub-elements.
  • Compositor class is unaware of its concrete classes and works with them through a common interface.
  • The Component might contain the reference to its parent element in the tree, which should be set once the component is added to the tree.
  • The Composite container can cache the results of its nested component operations to improve the performance of the tree traversal. In this case, any child component update should reset the cache of its parent containers.

Examples

πŸ‘” Decorator

Decorator allows attaching new behavior to objects by wrapping them into a special method that contains this additional functionality.

Decorator pattern is designed around the wrapper object, which is linked with some target one and delegates all the invocations to it. The wrapper object has the same methods and interfaces as the wrapped one.

Class Diagram

Decorator Class Diagram

Structure

  • IComponent interface describes class methods that will be decorated
  • ConcreteComponent is the decorated object class
  • Decorator abstract class stores the component instance and declares the same interface as an original object
  • ConcreteDecorator implements additional functionality added to the component

When to use

  • To assign extra behaviors to existing objects at runtime without making inherited classes
  • To add functionality to a particular object, not to the general class
  • When it is impossible to apply the inheritance for extending class logic
  • When a class needs to have a number of small independent extensions

Implementation

Notes

  • Decorator must add new functionality to the object methods without changing their interface.
  • A few decorators can be applied on the same object sequentially as they represent additional independent logic layers.
  • An abstract Decorator class can be skipped if only one ConcreteDecorator is planned.

Examples

🏬 Facade

Facade provides a simplified interface for accessing libraries, frameworks, or complex sets of classes.

Facade Motivation

Class Diagram

Facade Class Diagram

Structure

  • Client uses System features through the Facade interface instead of calling them directly
  • Facade provides access to the specific System functionality
  • System represents a bunch of related classes and objects that operate together and has own initialization and communication logic

When to use

  • To avoid tight coupling between the application and the complex modules by connecting them using a limited interface
  • When you need a limited declarative interface for the independent systems without the knowledge of their internal details
  • When an external system needs to be included as a separate layer
  • To connect a few different systems and make them communicate via a single unified interface

Implementation

Notes

  • Facade pattern provides a unified access interface for a particular system, but the client can still use system components directly if needed.
  • The system, hidden by the Facade, knows nothing about it and doesn’t store any facade references.
  • Facade class can be declared abstract to allow different system usage variants without changing the client’s codebase.
  • Facade can be implemented as a singleton to initiate only one set of system classes.

Examples

πŸƒ Flyweight

Flyweight pattern minimizes memory usage by sharing common data between multiple similar objects instead of storing all the details in each object.

Object state consists of the following parts:

  • Intrinsic state - the constant data that other objects can only read but not modify.
  • Extrinsic state - the data that can be set and accessed by other objects.

Flyweight suggests extracting the extrinsic state from the object and passing it to specific methods which rely on it. Only the intrinsic state should be stored in the object to be reused in different contexts. As a result, the system will have fewer flyweight objects as they only differ in the intrinsic state.

The object that only stores an intrinsic state is called a flyweight.

The extrinsic state can be stored in a container object or a separate context class aggregating these properties and having a reference to the particular flyweight object.

Flyweight object has to be immutable and does not allow to change its state via setters and/or public properties.

Usually, the flyweight objects management is implemented via a factory method that applies an intrinsic state as an argument and returns the desired object (newly initiated or already existing in the factory’s cache).

Class Diagram

Flyweight Class Diagram

Structure

  • Flyweight interface declares the part of the object state that can be shared (intrinsic state) and the operations depending on a unique external state (extrinsic state)
  • ConcreteFlyweight class implements the Flyweight interface and represents concrete flyweight objects
  • UnsharedConcreteFlyweight is an optional implementation of the Flyweight interface that contains both intrinsic state_ and extrinsic state
  • FlyweightFactory stores already created flyweight objects or initializes new ones based on a shared portion of data (intrinsic state)
  • Context class stores a portion of unique data (extrinsic state) along with the corresponding ConcreteFlyweight object

When to use

  • When the system operates a vast number of heavy objects, which affects the available RAM
  • When there are a lot of similar objects having a common piece of the state which can be shared
  • When some properties are duplicated among the object and hold large data (binaries, graphics)
  • To create and manage a lot of tiny objects

Implementation

Notes

  • Flyweight objects with their intrinsic (shared) state must be immutable.
  • Flyweight approach might save RAM, but it affects CPU resources for the runtime computations.
  • Flyweight provides a shared interface but does not force it. Therefore, it’s possible to have flyweight objects with an unshared state if needed.

πŸ›‘ Proxy

Proxy provides a placeholder for another object. It controls access to the original object and can perform some operations in between.

Class Diagram

Proxy Class Diagram

Structure

  • IService interface describes the methods of the Service class that will be proxied
  • Service is the original class to be proxied
  • Proxy implements IService interface and stores Service instance to pass the incoming requests to it
  • Client works with the Proxy object via the IService interface

When to use

  • To implement custom functionality over third-party objects
  • For the lazy initialization to postpone the service run right after the system starts
  • For access control to prevent or limit particular object usage by different actors
  • When a few services work with the same object to lock the object while it is currently in use
  • To monitor and log the requests to an object and track object users
  • To cache time-consuming operation results (for example, database queries or network requests)

Implementation

Notes

  • Proxy and Decorator patterns have a similar structure but differ in their intentions.
  • Proxy manages the object lifecycle on its own, while Decorator is controlled by the client.

Examples