Design Pattern Evangelist Blog

Smart pointers about software design

Composable Design Patterns – Basic Concepts

Code reuse and dynamic flexibility via composition


Full Adder Logic Gates

Introduction

This blog entry begins a new series of Design Patterns. I refer to these design patterns as Composable Design Patterns, which feature object composition. That is, behavior emerges from the interaction of a set objects organized in a cohesive pattern.

Consider logic gates as shown above. Binary addition emerges from the interaction among these logic gates. Repeating patterns can be grouped into their own logical components, such as Full and Half Adders. Logic gates can be organized in different combinations from which emerges all behaviors electronic computers depend upon.

Code Reuse

Code reuse has been the Holy Grail of software since I started my career in the mid-1980s. I recall a kick-off meeting from that time for the second release of a project. One of the project managers was giving us a pep talk. We were considering an almost entire rewrite of the first release. The project manager challenged us. Why did we have to start from scratch? The project manager didn’t just want evolutionary code. He wanted revolutionary code! For context, our project was written in C, and we just didn’t know how to design for reuse at the time.

We, as an industry, have made significant improvements with reuse through frameworks and utility libraries. But this is generic reuse. It isn’t reuse of our business domain components. How can we reuse our business domain concepts?

Inheritance

Object-Oriented (OO) techniques held the promise of reuse with inheritance. I think that code reuse through inheritance was one of the reasons that OO was widely adopted in the 1990s. However, inheritance wasn’t necessarily the panacea that we thought it might be. While inheritance did afford some code reuse, it came with a cost. Reuse was statically locked into place. The inheritance hierarchies could become unyieldingly large. It violated encapsulation. Changes in ancestor classes could break previously working behavior in the descendant classes. And when descendant classes overrode behavior of their ancestor classes, they ran the risk of violating the Liskov Substitution Principle.

These issues reside with implementation inheritance. Interface inheritance doesn’t have these issues.

Object Composition

The Design Pattern Gang of Four (GoF) authors approached reuse via another approach, which is embedded in their two design principles: Program to an interface, not an implementation and Favor object composition over class inheritance.

The first principle was featured in the Essential Design Patterns. It is not ignored with the Composable Design Patterns either, since every Composable Design Pattern features an interface or abstract base class as the root element. But the Composable Design Patterns pick up where the Essential Design Patterns left off. Their main feature highlights the second design principle to favor object composition over inheritance.

I’m sure the GoF chose “favor” for a reason. Do not eschew inheritance but compare it against object composition. Favor object composition, but inheritance is still an option.

E Pluribus Unum

An interface defines contract behavior. We often implement it with a class, which might delegate to other classes as well.

But we have another design approach that may be a bit more modular. Consider that we can implement the contract behavior using a group of cohesive objects instantiated from low-level classes where the desired behavior emerges from the interaction of these cohesive objects. No individual object is responsible for the entire behavior and yet all of them are responsible for it collectively.

The low-level elements can be reused in different combinations to define different emergent behaviors. Behavior emerges from the collective cohesive objects and not from the implementation within one individual class.

Emergent behavior distributed across multiple cohesive objects is probably the most challenging aspect of Composable Design Patterns. The challenge is not in the implementation itself. The challenge is in the comprehension of distributed behavior across multiple objects.

Inheritance versus Composition

Inheritance and Composition both feature reuse, but they achieve it in different ways:

The use of vertical and horizontal visual comes from UML class diagrams. We tend to draw inheritance vertically can composition horizontally.

Atoms and Molecules and Minecraft

DNA Molecules

A real-world example of composability is chemistry. Atoms are the low-level elements from which molecules can be assembled to produce all kinds of chemical behaviors, including life. Our low-level objects are like elements on the Periodic Table. Each has a unique simple behavior. Then low-level objects can be assembled to create more complex behaviors from those simple behaviors. Just as many different complex molecules spring forth from the finite set of elements in the Periodic Table, many different complex behaviors can spring forth from the finite set of low-level objects.

Object composition is front-and-center in Minecraft with its redstone material as described on the Minecraft Wikipedia page:

The game also contains a material known as redstone, which can be used to make primitive mechanical devices, electrical circuits, and logic gates, allowing for the construction of many complex systems.

With the introduction of redstone blocks to represent electrical circuits, users have been able to build functional virtual computers within Minecraft. Such virtual creations include a working hard drive, an 8-bit virtual computer, and even a smaller-scale version of Minecraft that is playable and able to be built completely in survival mode with no external modifications. In at least one instance, a mod has been created to use this feature to teach younger players how to program within a language set by a virtual computer within a Minecraft world.

Another computational mechanic in Minecraft is the command block, a block that is only accessible in creative mode and can alter game logic. It has been used to create emulators for the Atari 2600 (including one by YouTube personality SethBling) and the Game Boy Advance.

Composable Design Patterns

The GoF did not define a set of Composable Design Patterns per-se. This is my grouping, just like the Essential Design Patterns is my grouping as well. I’m not even sure that this is the best name for these patterns. I’ve also called them the Reuse/Reuseable/Reusability Design Patterns.

Regardless of the name, here are the patterns, which I feel most highlight the concept of composability. They are listed in order of generally least complexity to most complexity. Several patterns expand upon concepts that first appear in previous ones as well. I will feature each of these in this blog series:

Additionally, there are some two additional creational patterns, which I may include in this series as well, since they are useful for these patterns:

Composition Structure

Object composition is more than just making a single method call to another object. With Composable Design Patterns, behavior aggregates across multiple objects organized within basic data structures, such as linked lists or trees. However, there’s one slight data structure variation. Traditional data structures contain data. Composable Design Patterns data structures contain functional behaviors as we’ve seen with Command/Strategy. Composition isn’t about the classes. It’s about the composition of the objects instantiated from the classes. Different compositions of objects yield different behaviors.

This means that the same implementation of classes can yield different behavior based upon different compositions of objects instantiated from those classes. This is not different behavior via if and switch statements or feature flags. It’s configurable behavior that emerges from object composition rather than different execution paths through traditional code.

Composition Basics

The Composable Design Patterns are an extension of several Essential Design Patterns such as Command, Strategy, Template Method and a little bit of Adapter. These patterns feature concrete classes, which implement an interface or extend an abstract class.

The Composable Design Patterns feature similar concrete classes as well as delegation, except that the delegation is self-referential. They delegate to elements in the design often their root interface or class.

Here’s a UML class diagram that highlights this self-referential delegation. Each of the specific Composable Design Patterns will be slightly different based upon their own contexts than this example, but they will contain similar structures:

Composable Design Patterns Template

Non-Configurer concrete classes only know about interfaces. New concrete classes can be added without affecting existing classes or existing configurations. All the classes can be easily unit tested.

The classes in these patterns tend to be stateless. Each can be viewed as a pure function. Therefore, their composition can be viewed as a composition of pure functions. This makes them ideal for concurrent implementations. Multiple threads can execute simultaneously within the set of objects. The composition might only need to be composed once and then used repeatedly by many threads without additional concern.

Computation and Coordination

Composition has more flexibility than inheritance, but with that comes more runtime responsibility. The composition won’t happen by default. Composable patterns are divided into two concepts:

  1. Computation via code reuse using delegation.
  2. Coordination of the objects that comprise that composition.

Computation resides in the class implementation. Composition resides with the Configurer as described above. Keep these two concepts separate.

Kevlin Henney pointed out the following in his GOTO 2018 presentation, Old is the New New:

We can build a complete programming model out of two separate pieces – the computation model and the coordination model.

Coordination Languages and their Significance”, David Gelernter and Nicholas Carriero, published in the Communications of the ACM in 1992.

This computation/coordination model concept appears in many layers of abstraction in computing:

The computation/coordination model concept will continue throughout the Design Patterns. It’s almost identical to the Design Pattern principles above:

Behavior emerges from the coordination/composition of objects. The same objects in different compositions will yield different behaviors. Composition management may reside on a spectrum from development organization composition to customer [support] organization composition to customer self-service composition to individual user self-service composition. As the composition management moves away from development and closer to the customer and user, the definition and responsibility of desired behavior moves further from development and closer to the customer and user.

While some compositions can be tested for sanity, it may not be possible to test all possible behaviors. And we may never know the behaviors composed by customers or users via self-service. I will blog (TBD) about this in the future.

Use Cases for Composability Design Patterns

These patterns are marvelous. A relatively small implementation provides so much functional potential. These patterns not only provide the potential means of code reuse for new features, but they also provide the possibility of customized features for customers and users as well.

Customized rule or policy-based behaviors are possible. With care, a single code base can support different policy requirements for different customers or even users. Policy behavior can be supported independently for different users and be changed without having to change the implementation. This is not the same as config values, feature flags or branching logic in the implementation. Policy based behaviors tend to reside in specifications, which are rendered as different configuration assemblies from a finite set of implementation objects.

In some cases, the configuration is completely within control of the project and never exposed externally. In other cases, the configuration options might be presented to the customer or user for their own self-service configurations.

Insurance Industry

The insurance industry can benefit from this flexibility with their insurance policies, as in auto, home, boat, etc. Insurance companies offer many types of insurance, many with the potential for additional riders. Making matters even more complicated, the insurance industry is highly regulated with government policy regulations varying among countries, states, provinces, etc.

It’s possible for each insurance policy holder to have a unique policy combination unlike any other insurance policy holder in the system. It’s impossible to program all possible insurance policy scenarios with traditional programming. But the compositional flexibility of design patterns could allow a single code base to support any number of potential insurance policies. They could support individualized billing, policy statements and other insurance elements.

Gary Hoberman, CEO and Founder of Unqork, spoke about this in the Thoughtworks Pragmatism in Practice Podcast episode: No-code platforms and the art of the possible. He mentioned this aspect specifically in how his company addressed the insurance industry starting at around minute 25:00 into the podcast and lasting for about 4 minutes. While Hoberman doesn’t discuss specific design concerns, what he does describe sounds 100% consistent with composable design patterns.

User Data Rights Regulations

User data rights are highly rule/policy based. User data rights are subject to external forces, such as government regulations. They are prone to be segregated and segmented, for example the rights in GDPR, California and other jurisdictions are similar, but there are still subtle differences. They are prone to change with tight inflexible deadlines for conformance with nonconformance resulting in lost revenue or fines. Being regulatory, they tend to be vague, ambiguous and/or incomplete, so you may think you have it right, only to learn in the 11th hour that something is nonconformant and you need to make a quick update. Configuration updates are faster than implementation updates.

A rule/policy-based implementation for user data rights would consist of individual specific low-level user data rights behaviors that are configured into composable behaviors that manage each user’s specific rights. As policies change, the specification for the composition would change, which may not require implementation updates. If regulations require new low-level user data rights classes, then they can be added without touching or affecting the existing low-level classes or their object composites until new composite specifications are defined that include those new low-level classes.

Self-Service Apps and Kiosks

Fast-Food Kiosk Dagwood Sandwich

Many fast-food establishments allow you to customize your order via an app or kiosk. These often provide options well beyond A, B or C static menu options. For example, options for sandwich establishments often allow the customer to design their own sandwich at the app/kiosk. The app/kiosk might start with the concept of a sandwich, but then the customer can choose bread, meat, cheese, condiments, etc. Or for simplicity, a sandwich configuration might be predefined, and the customer only needs to choose optional condiments.

Summary

This has laid the foundation for the Composable Design Patterns. Subsequent blogs will provide more concrete details with context as we explore them.

Comments

Previous: Bumper Sticker Computer Science and Software Engineering

Next: Proxy Design Pattern

Home: Design Pattern Evangelist Blog