Design Pattern Classifications Part 2 — Structural Pattern

Avelon Pang
Nerd For Tech
Published in
7 min readMay 25, 2021

--

In my previous articles we discussed the GoF, design patterns and reviewed the creational pattern. For this article we will briefly review classifications of design patterns before taking a closer look at the structural patterns. There will be an overview of the seven (classic) structural design patterns as well as the pros and cons to each.

Classification Review

Not only are design patterns useful, but are considered best practices by experienced object-oriented software developers. Since design patterns differ by their level of detail, complexity and scale of applicability, it is important to categorize them by their intent, or purpose. The purpose criteria reflects what the pattern does. The three main groups of patterns are creational patterns, structural patterns and behavioral patterns. However, for the purpose of this article we will be focusing on the structural design pattern.

Structural Patterns

Structural patterns explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient. These patterns help make it easier to identify a simple way to realize relationships between entities. Let’s review the applicability, benefits, and concerns of the following structural patterns.

Adapter

The adapter pattern allows objects with incompatible interfaces to collaborate, essentially making things work after they’re already designed. Basically, adapters make this possible by converting (adapting) the interface of one object so that another object can understand it. This is accomplished by the adapter wrapping one of the objects, hiding the complexity of conversion happening behind the scenes without the wrapped object knowing. Not only can adapters help objects with different interfaces collaborate, but also convert data into various formats.

  • Object Adapter — uses the object composition principle to implement the interface of one object and wraps the other one. This can be used with any popular programming language.
  • Class Adapter — uses the inheritance implementation to inherit interfaces from both objects at the same time. This can only be applied if the programming language supports multiple inheritance. Unlike Object Adapters, it does not need to wrap any objects because it inherits behaviors from both the client and service.

Pros

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

Cons

  • The complexity of the code increases with every new interface and class you introduce to your code.

Bridge

The bridge pattern is designed up-front to divide a large class (or set of related classes) into two hierarchies of abstraction and implementation, which can vary because they can be developed independently of each other. This pattern should be considered if you want to separate an object’s interface from its implementation or if you want to divide and organize a monolithic class that has several variants of some functionality. This design can also be effective if you need to be able to switch implementations at runtime or want to extend a class in several independent dimensions.

Pros

  • Single Responsibility Principle — focus on high-level logic in the abstraction
  • Open/Closed Principle — introduce new abstraction and implementations independently from each other
  • Create platform-independent classes

Cons

  • Potential to complicate code by applying the pattern to a highly cohesive class

Composite

This composite pattern lets you compose objects into tree structures and then work with these structures as if they were individual objects. This pattern should be applied when you want to implement a tree-like object structure because it provides two basic element types that share a common interface. Since a container can be composed of both leaves and other containers, this lets you construct nested recursive object structures (just like a tree)! This is a great approach if you want the client code to treat both simple and complex elements uniformly.

Pros

  • Open/Closed Principle — introducing new element types without breaking existing code
  • Use polymorphism and recursion to your advantage

Cons

  • Might be challenging to provide a common interface for classes when functionalities differ greatly
  • The possibility of overgeneralizing the component interface for two differing interfaces could make it harder to comprehend

Decorator

The intent of this pattern is allowing you to attach new behaviors to objects by placing them inside a special wrapper object that contains the behaviors. The wrapper is an object that expresses the main idea of the pattern and can be linked with some target object. This is a good pattern to implement if you want to compress and encrypt sensitive data independently from code that actually uses the data. Other opportunities to use the decorator pattern is when you need to assign extra behaviors to objects at runtime without breaking the code. Essentially, this pattern adds responsibilities to objects dynamically.

Pros

  • Single Responsibility Principle — division of a monolithic class that implements many possible variants of behavior into smaller classes
  • Combine multiple behaviors by wrapping an object into multiple decorators
  • Extend an object’s behavior without making a new subclass
  • Add or remove responsibilities from an object at runtime

Cons

  • May be challenging to remove a specific wrapper from the wrappers stack
  • Initial configuration code of layers may look unpleasant
  • Difficulty implementing a decorator without its behavior depending on the order of the decorator’s stack

Proxy

This structural pattern is a class representing the functionality of another class by providing a placeholder for another object. A proxy’s pattern controls access to the original object, allowing you to perform something either before or after the request gets through to the original object. In other words, this pattern controls and manages access to the object they are projecting. There are many situations where the proxy design should be considered. For instance, when you only want specific clients to be able to use the service object, want to keep a history or requests to the service object or need to be able to dismiss a heavyweight object once no clients that use it.

Pros

  • Open/Closed Principle
  • Does not depend on the service object to be ready or available for it to work
  • Manage the lifecycle of the service object when client’s don’t care about it
  • Control the service object without clients knowing about it

Cons

  • High risk of the code becoming more complicated with every introduction of a new class
  • Chances of a delayed response from the service
The operator acts as a facade to all services and departments of the company

Facade

The facade pattern is an object that provides a simple interface to a larger body of code, like a library, a framework or generally a more complex underlying object without introducing any new functionality. Essentially, this is a single class that represents an entire subsystem without the subsystem’s awareness of the facade. This approach is usually suggested if you need to have a limited but straightforward interface to a complex subsystem or want to structure a subsystem into layers.

Pros

  • Isolation of your code from the complexity of a subsystem

Cons

  • Possibility of a facade becoming a god object (an object that knows too much or does too much) to all classes of an app.

Flyweight

Flyweight is used for efficient sharing because it lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects rather than keeping all the data in each individual object. In other words, the flyweight pattern is used to minimize memory usage or computational expenses by sharing as much as possible with similar objects. Flyweight also explains when and how state objects can be shared. This is a pattern often used when a program must either support a huge number of objects which barely fit into available RAM, needs to spawn a huge number of similar objects or if the objects with duplicate states can be extracted and shared between multiple objects.

Pros

  • If your program has several similar objects, it can save lots of RAM

Cons

  • Increase in code complexity
  • Might be trading RAM over CPU cycles when some of the context data needs to be recalculated each time the flyweight method is called
  • New team members may wonder why state of an entity was separated in such a way

Conclusion

In conclusion, structural design patterns are concerned with how classes and objects can be composed to form larger structures. These patterns focus on how the classes inherit from each other and simplifies the structure by identifying the relationships. Now that you’re familiar with the application, benefits and drawbacks of seven structural design patterns, I encourage you to dive deeper by continuing your research (more resources provided below) and to only implement the design patterns that simplify your code.

Stay tuned for Design Pattern Classifications Part 3— Behavioral Pattern

Previous Articles:

Additional Resources:

Happy Coding!

--

--

Avelon Pang
Nerd For Tech

Full stack software developer with a passion for applying new technologies and a zest for technical problem solving. Bilingual in English and Mandarin.