Skip to content

3. Structural design patterns

Structural patterns explain how to assemble objects and classes into larger structures, while keeping these structures flexible and efficient.

Adapter

Definition

Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.

Object adapter

uml diagram

Class adapter

uml diagram

Implementation Details

Object adapter

  1. The Client is a class that contains the existing business logic of the program.
  2. The Client Interface describes a protocol that other classes must follow to be able to collaborate with the client code.
  3. The Service is some useful class (usually 3rd-party or legacy). The client can’t use this class directly because it has an incompatible interface.
  4. The Adapter is a class that’s able to work with both the client and the service: it implements the client interface, while wrapping the service object. The adapter receives calls from the client via the adapter interface and translates them into calls to the wrapped service object in a format it can understand.
  5. The client code doesn’t get coupled to the concrete adapter class as long as it works with the adapter via the client interface. Thanks to this, you can introduce new types of adapters into the program without breaking the existing client code. This can be useful when the interface of the service class gets changed or replaced: you can just create a new adapter class without changing the client code.

Class adapter

  1. The Class Adapter doesn’t need to wrap any objects because it inherits behaviors from both the client and the service. The adaptation happens within the overridden methods. The resulting adapter can be used in place of an existing client class.

Pros and Cons

Pros

  • Single Responsibility Principle. You can separate the interface or data conversion code from the primary business logic of the program.
  • Open/Closed Principle. You can introduce new types of adapters into the program without breaking the existing client code, as long as they work with the adapters through the client interface.

Cons

  • The overall complexity of the code increases because you need to introduce a set of new interfaces and classes. Sometimes it’s simpler just to change the service class so that it matches the rest of your code.

Usage

  • Use the Adapter class when you want to use some existing class, but its interface isn’t compatible with the rest of your code.
  • Use the pattern when you want to reuse several existing subclasses that lack some common functionality that can’t be added to the superclass.

Composite

Definition

Composite is a structural design pattern that lets you compose objects into tree structures and then work with these structures as if they were individual objects. uml diagram

Implementation Details

  1. The Component interface describes operations that are common to both simple and complex elements of the tree.
  2. The Leaf is a basic element of a tree that doesn’t have sub-elements.
  3. Usually, leaf components end up doing most of the real work, since they don’t have anyone to delegate the work to.
  4. The Container (aka composite) is an element that has sub-elements: leaves or other containers. A container doesn’t know the concrete classes of its children. It works with all sub-elements only via the component interface.
  5. Upon receiving a request, a container delegates the work to its sub-elements, processes intermediate results and then returns the final result to the client.
  6. The Client works with all elements through the component interface. As a result, the client can work in the same way with both simple or complex elements of the tree.

Pros and Cons

Pros

  • You can work with complex tree structures more conveniently: use polymorphism and recursion to your advantage.
  • Open/Closed Principle. You can introduce new element types into the app without breaking the existing code, which now works with the object tree.

Cons

  • It might be difficult to provide a common interface for classes whose functionality differs too much. In certain scenarios, you’d need to overgeneralize the component interface, making it harder to comprehend.

Usage

  • Use the Composite pattern when you have to implement a tree-like object structure.
  • Use the pattern when you want the client code to treat both simple and complex elements uniformly.

Proxy

Definition

Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object.

uml diagram

Implementation Details

  1. The Service Interface declares the interface of the Service. The proxy must follow this interface to be able to disguise itself as a service object.
  2. The Service is a class that provides some useful business logic.
  3. The Proxy class has a reference field that points to a service object. After the proxy finishes its processing (e.g., lazy initialization, logging, access control, caching, etc.), it passes the request to the service object.
  4. Usually, proxies manage the full lifecycle of their service objects.
  5. The Client should work with both services and proxies via the same interface. This way you can pass a proxy into any code that expects a service object.

Pros and Cons

Pros

  • You can control the service object without clients knowing about it.
  • You can manage the lifecycle of the service object when clients don’t care about it.
  • The proxy works even if the service object isn’t ready or is not available.
  • Open/Closed Principle. You can introduce new proxies without changing the service or clients.

Cons

  • The code may become more complicated since you need to introduce a lot of new classes.
  • The response from the service might get delayed.

Usage

  • Lazy initialization (virtual proxy). This is when you have a heavyweight service object that wastes system resources by being always up, even though you only need it from time to time.
  • Access control (protection proxy). This is when you want only specific clients to be able to use the service object; for instance, when your objects are crucial parts of an operating system and clients are various launched applications (including malicious ones).
  • Local execution of a remote service (remote proxy). This is when the service object is located on a remote server.
  • Logging requests (logging proxy). This is when you want to keep a history of requests to the service object.
  • Caching request results (caching proxy). This is when you need to cache results of client requests and manage the life cycle of this cache, especially if results are quite large.
  • Smart reference. This is when you need to be able to dismiss a heavyweight object once there are no clients that use it.

Flyweight

Definition

Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object.

uml diagram

Implementation Details

  1. The Flyweight pattern is merely an optimization. Before applying it, make sure your program does have the RAM consumption problem related to having a massive number of similar objects in memory at the same time. Make sure that this problem can’t be solved in any other meaningful way.
  2. The Flyweight class contains the portion of the original object’s state that can be shared between multiple objects. The same flyweight object can be used in many different contexts. The state stored inside a flyweight is called intrinsic. The state passed to the flyweight’s methods is called extrinsic.
  3. The Context class contains the extrinsic state, unique across all original objects. When a context is paired with one of the flyweight objects, it represents the full state of the original object.
  4. Usually, the behavior of the original object remains in the flyweight class. In this case, whoever calls a flyweight’s method must also pass appropriate bits of the extrinsic state into the method’s parameters. On the other hand, the behavior can be moved to the context class, which would use the linked flyweight merely as a data object.
  5. The Client calculates or stores the extrinsic state of flyweights. From the client’s perspective, a flyweight is a template object which can be configured at runtime by passing some contextual data into parameters of its methods.
  6. The Flyweight Factory manages a pool of existing flyweights. With the factory, clients don’t create flyweights directly. Instead, they call the factory, passing it bits of the intrinsic state of the desired flyweight. The factory looks over previously created flyweights and either returns an existing one that matches search criteria or creates a new one if nothing is found.

Pros and Cons

Pros

  • You can save lots of RAM, assuming your program has tons of similar objects.

Cons

  • You might be trading RAM over CPU cycles when some of the context data needs to be recalculated each time somebody calls a flyweight method.
  • The code becomes much more complicated. New team members will always be wondering why the state of an entity was separated in such a way.

Usage

  • Use the Flyweight pattern only when your program must support a huge number of objects which barely fit into available RAM.

Facade

Definition

Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes. uml diagram

Implementation Details

  1. The Facade provides convenient access to a particular part of the subsystem’s functionality. It knows where to direct the client’s request and how to operate all the moving parts.
  2. An Additional Facade class can be created to prevent polluting a single facade with unrelated features that might make it yet another complex structure. Additional facades can be used by both clients and other facades.
  3. The Complex Subsystem consists of dozens of various objects. To make them all do something meaningful, you have to dive deep into the subsystem’s implementation details, such as initializing objects in the correct order and supplying them with data in the proper format.
  4. Subsystem classes aren’t aware of the facade’s existence. They operate within the system and work with each other directly.
  5. The Client uses the facade instead of calling the subsystem objects directly.

Pros and Cons

Pros

  • You can isolate your code from the complexity of a subsystem.

Cons

  • A facade can become a god object coupled to all classes of an app.

Usage

  • Use the Facade pattern when you need to have a limited but straightforward interface to a complex subsystem.
  • Use the Facade when you want to structure a subsystem into layers.

Bridge

Definition

Bridge is a structural design pattern that lets you split a large class or a set of closely related classes into two separate hierarchies—abstraction and implementation—which can be developed independently of each other. uml diagram

Implementation Details

  1. The Abstraction provides high-level control logic. It relies on the implementation object to do the actual low-level work.
  2. The Implementation declares the interface that’s common for all concrete implementations. An abstraction can only communicate with an implementation object via methods that are declared here.
  3. The abstraction may list the same methods as the implementation, but usually the abstraction declares some complex behaviors that rely on a wide variety of primitive operations declared by the implementation.
  4. Concrete Implementations contain platform-specific code.
  5. Refined Abstractions provide variants of control logic. Like their parent, they work with different implementations via the general implementation interface.
  6. Usually, the Client is only interested in working with the abstraction. However, it’s the client’s job to link the abstraction object with one of the implementation objects.

Pros and Cons

Pros

  • You can create platform-independent classes and apps.
  • The client code works with high-level abstractions. It isn’t exposed to the platform details.
  • Open/Closed Principle. You can introduce new abstractions and implementations independently from each other.
  • Single Responsibility Principle. You can focus on high-level logic in the abstraction and on platform details in the implementation.

Cons

  • You might make the code more complicated by applying the pattern to a highly cohesive class.

Usage

  • Use the Bridge pattern when you want to divide and organize a monolithic class that has several variants of some functionality (for example, if the class can work with various database servers).
  • Use the pattern when you need to extend a class in several orthogonal (independent) dimensions.
  • Use the Bridge if you need to be able to switch implementations at runtime.

Decorator

Definition

Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors. uml diagram

Implementation Details

  1. The Component declares the common interface for both wrappers and wrapped objects.
  2. Concrete Component is a class of objects being wrapped. It defines the basic behavior, which can be altered by decorators.
  3. The Base Decorator class has a field for referencing a wrapped object. The field’s type should be declared as the component interface so it can contain both concrete components and decorators. The base decorator delegates all operations to the wrapped object.
  4. Concrete Decorators define extra behaviors that can be added to components dynamically. Concrete decorators override methods of the base decorator and execute their behavior either before or after calling the parent method.
  5. The Client can wrap components in multiple layers of decorators, as long as it works with all objects via the component interface.

Pros and Cons

Pros

  • You can extend an object’s behavior without making a new subclass.
  • You can add or remove responsibilities from an object at runtime.
  • You can combine several behaviors by wrapping an object into multiple decorators.
  • Single Responsibility Principle. You can divide a monolithic class that implements many possible variants of behavior into several smaller classes.

Cons

  • It’s hard to remove a specific wrapper from the wrappers stack.
  • It’s hard to implement a decorator in such a way that its behavior doesn’t depend on the order in the decorators stack.
  • The initial configuration code of layers might look pretty ugly.

Usage

  • Use the Decorator pattern when you need to be able to assign extra behaviors to objects at runtime without breaking the code that uses these objects.
  • Use the pattern when it’s awkward or not possible to extend an object’s behavior using inheritance.