Design Pattern Evangelist Blog

Smart pointers about software design

DRAFT – What Is Cohesive Abstraction?

How to manage cohesion among abstractions consistently


Introduction

This blog is a continuation of the previous Abstract blog where I follow up with Cohesive Abstractions.

Abstractions may reference other Abstractions, depend upon other Abstractions or have knowledge of them. We want to ensure that when this occurs that the concrete implementations for these Abstracts are resolved consistently.

Cohesion and Coupling

Before I describe the consistent management of Cohesive Abstractions, I need to explain Cohesion and Coupling.

Cohesion

It took me a while before I had a solid understanding of Cohesion.

Elements are cohesive when there’s an intrinsic relationship that connects them. The relationship is often gestalt in that the elements only have meaningful utility when combined as a whole. When an update is needed, most or all elements in the cohesive relationship will require updating.

Screw and Screwdriver

The intrinsic relationship between screw and screwdriver is an example. Neither is functional without the other. The screwdriver must also match the type of screw. For example, a flathead screwdriver won’t work on a Phillips head screw. If the design changes from a flathead screw to a Phillips head screw, then the screwdriver will need to change from a flathead to a Phillips head likewise.

Nuts and Bolts

Nuts and bolts are cohesive. They only function when screwed together, and when their sizes match.

We want our designs to have high cohesion. That is, software elements with intrinsic relationships should be near one another in the design.

Low cohesion occurs when related software elements have been distributed across the design. For example, you’re enhancing an existing behavior, and it requires changes to a dozen files distributed throughout the design. Additionally, the files being updated also contain code that’s not directly related to the behavior that you’re updating.

Low cohesion has at least two concerns:

Another common example of low cohesion I’ve seen is state machine behavior (blog TBD) spread across the implementation. This usually happens when a class has a private attribute named something like status or state. State Machine Diagram Its type is often an enum, but I’ve also seen it as a boolean and even a String. Its implied privacy is violated with get and set accessors, which provide direct access to the private attribute.

Rather than keeping the state machine behavior encapsulated within the class, the class becomes the place where state or status reside. Other classes will access status/state via the get accessor, update status/state with a new value via the set accessor based upon their own business logic code. Not only does this low cohesive design distribute the state machine implementation across far flung regions of the design, it distributes behavior. This makes it more difficult to know what the state machine will do as a whole when the status/state is updated from any place in the implementation.

With cohesion software elements, a change to one element may require a change to the other related cohesive elements, like how a change in screw head causing a change in the screwdriver. Highly cohesive software elements are close to one another, such as in the same package, making it more likely that all required updates will occur consistently.

I consider the methods of an interface cohesive when:

Traditional Create, Read, Update and Delete (CRUD) operations are cohesive in my mind. Remove any of them, and the remaining operations are incomplete. The requirements for one of my features contained Create, Update and Delete requirements for a set of domain elements, but there were no Read requirements. I asked my requirements creator why I was implementing features to manage data when there were no requirements to read the data. Oops.

A cohesive interface tends to follow the Interface Segregation Principle (ISP). An interface that violates ISP contains methods that can be separated into their own cohesive interfaces.

Coupling

Coupling is often uttered in the same breath with cohesion. It took me a while to gain a solid understanding of coupling, especially when used with cohesion. They seemed similar yet different, and I wasn’t sure how to distinguish them.

Coupling is also about connected elements as well. However, unlike cohesive elements, coupled elements do not intrinsic relationships.

Reece's Cup

A Reese’s Peanut Butter Cup couples chocolate and peanut butter together. I know for many, a peanut butter cup would be an excellent example of cohesion, but I’m the odd duck who doesn’t like peanut butter cups, even though I like chocolate and peanut butter separately.

We want our designs to have loose coupling. That is, we don’t want unrelated elements to be stuck together too tightly.

A Reese’s Cup would be tightly coupled if it were the only form from which we could get chocolate or peanut butter. Imagine scraping the insides from a Reese’s Cup as the only means to make a peanut butter sandwich.

Tight coupling occurs when disparate software concepts are stuck together. For example, the communication framework, business logic and persistence implementations are intertwined in the same method. A change to the communication framework may affect the business logic. A change in the persistence my affect the communication framework, etc. Reusing the business logic in another context becomes impossible.

Many modern software practices, such as Design Patterns, Hexagonal Architecture/Ports & Adapters, and others address tight coupling. They promote loose coupling. The first Design Pattern Principle addresses this, even if it doesn’t mention coupling directly: Program to an interface, not an implementation.

Coupling and Cohesion

Tight coupling and low cohesion, as described above, are undesirable design traits.

A good design features loose coupling of software elements that don’t change together and high cohesion for those software elements that do change together.

This blog will focus upon high cohesion among abstractions and how to ensure consistency.

Cohesive Abstractions

Cohesive Abstractions are when the coupled elements are abstractions, such as interfaces. I’ll continue with an example to demonstrate these concepts.

Call To Duty

Let’s assume that you’re a developer on a military game, such as Call to Duty, and you’re in the Weapons development team.

You’ll want to design a gun that supports behaviors, such as: ready, aim and fire.

Military Guns

You realize there can be multiple guns, so you want some degree of abstraction.

But not everything is a traditional gun. What about bazookas? Or what about previous weapon technology, such as the musket, or even the bow and arrow?

These can all be concrete examples of cohesive abstractions of a Launcher and Projectile. Some concrete pairs could be: Gun/Bullet, Bazooka/Shell, Musket/Ball, Bow/Arrow, Sling/Shot, etc.

Ready, aim and fire don’t quite work for some of these, so let’s generalize them with: load, aim and launch. This first design describes the cohesive abstractions:

Launcher/Projectile Abstract Cohesion

I think this is the first time in any of my blogs where I show one interface referencing another interface. Launcher has dependency knowledge of Projectile.

Launcher and Projectile are cohesive. A Launcher isn’t much use with a Projectile, and Projectile isn’t much use with a Launcher. They must be consistent. A Bazooka would not be of much use if its Projectile were a Bullet or Arrow.

The concrete implementations for Launcher and Projectile must be matching pairs.

Abstract Factory Design Pattern

One way to ensure matching pairs is via the Abstract Factory Design Pattern. I wrote about this pattern briefly in the Abstract Factory section of the Factory Design Patterns blog.

The Abstract Factory Design Pattern is the first pattern presented by the Gang of Four (GoF) in their Design Pattern book, because it’s alphabetically first. It’s a tough pattern to understand, which is one reason why I think the GoF’s book is difficult to learn design patterns.

Here are two good Abstract Factory Design Pattern online resources:

The Abstraction of the Abstract Factory Pattern

An Abstract Factory Pattern is an extension of the Factory Pattern concept.

A Factory is a class that returns a concrete instance for an abstract reference. An Abstract Factory is an interface that declares a set of Factory methods each of which return concrete instances for those Factory methods such that the set of returned references are consistent. Its abstraction supports different sets of instances consistently. See what I mean about this being a difficult pattern as the first one in the GoF.

It’s not quite as confusing as it sounds at first blush, but it requires some thought before one reaches understanding. I’ll layer in the concepts a few at a time.

Let’s start with the Abstract Factory interface that’s needed for the Launcher/Projectile pair. WeaponsSystem is the Abstract Factory in this example. It declares two methods that virtually create a Launcher and a Projectile. That is, these two methods declare an abstract contract to create references for these abstractions, but they don’t create the concrete instances themselves.

Abstract of Abstract Factory

Completing the Abstract Factory Design with Concrete Factories

The above only declares the abstractions. This diagram adds the concrete Factories that define the instantiate the concrete instances for Launcher and Projectile, which will be resolved in this example by a Rifle and Bullet respectively:

Full Design with Rifle

Another WeaponSystem Abstract Factory

The Abstract Factory requires more design elements. It’s an investment. Here is part of the return on that investment. When we want to add a Bazooka, we update the design as follows:

Full Design with Bazooka

The Rifle and Bazooka designs are identical in their Abstraction regions above the red dashed horizontal line. The distinctions below the line are almost boilerplate.

The addition of Bazooka system does not replace or invalidate the Rifle system. I did not show the previous Rifle related classes due to space concerns. This design can support as many Launcher/Projectile weapon system as the team imagines.

And Testing Too

We can easily test the code in the Abstract regions with the same design. The distinction is that it uses Test specific concrete classes:

Full Testing Design

Warrior with Multiple WeaponSystems

Rarely would a Warrior have only one WeaponSystem. Different WeaponSystems will have different attributes, such as:

The following design shows how a few updates to the previous designs can support a Warrior with multiple WeaponSystems with the assurance that the Launcher and Projectile will be consistent if each concrete WeaponSystem concrete class implements them consistently:

Multiple Weapon Design

Bigger Boom

I’m going to stay with the same design structure, but I’ll modify the context slightly. Instead of a Warrior with a hand weapon, this design features a CombatVehicle as a tank with a TankGun/TankGunShell pair.

Tank Design

Safety Critical

I moved from Warrior to CombatVehicle to feature a real-world example.

I worked on a military project where the goal was to move combat vehicles, such as tanks, with their crew via large military cargo planes. The crew could train by running simulations within their vehicles to familiarize themselves with terrain, landmark features, etc. of their destination during the hours in transit.

The simulations could involve spotting targets and firing their weapons. It would be very bad to fire a tank shell from inside the cargo plane. Our software would include a TRAINER mode so that the guns would not actually fire the shells. I didn’t work on these features directly.

Code like this would be considered Safety Critical, meaning that if there’s a bug in the code, it could seriously harm or kill people. Blowing a hole in the side of the plane during transit would definitely qualify as a Safety Critical concern.

Since I didn’t work directly on this code, I don’t know how they planned to implement this, but given other code I experienced on the project, I feared it might look something like the design below, where an if statement checks the mode and it only fires the TankGun when mode is not TRAINER.

Safety Critical Tank Design

I have several issues with this design:

Concrete Trainer Classes

We can think of TRAINER mode like Test Doubles. They are different versions of Launcher and Projectile. Here’s how they would appear in the design:

Safety Critical TankTrainer Design

This design is more cohesive. The if logic has been removed from the CombatVehicle.

However, there are still a few items that bug me:

Final Design For Now

I continued to think about the design. I have made a few enhancements to the design, which I will describe one design region at a time.

Overall Safety Critical Design

ABSTRACT

There are zero changes in the ABSTRACT region. That’s one of benefits of this design. Throughout this blog, the ABSTRACT region has barely changed.

CONCRETE

I wanted to address the complete separation of TankGun and TankGunTrainer in the previous designs. Often, we want complete separation of concrete classes, but I have this nagging feeling that these two classes, along with the TankShell/TankShellTrainer classes, should be cohesive.

TankGun and TankGunTrainer should exhibit nearly identical behaviors, with the distinction that TankGun launches the TankShell, whereas TankGunTrainer emulates launching the TankShell. If the two classes are separated, we run the risk of updating TankGun without updating TankGunTrainer. Their core behavior would be inconsistent. This could result a training simulation that behaves differently than it would when active in combat. We don’t want our tank crew to encounter any surprises when combat behaviors are different than training behaviors.

The previous design only used the Strategy Design Pattern for TankGun and TankGunTrainer. This updated design adds two more design patterns: Template Method and Adapter.

TankGun has been modified from a concrete class to an abstract class. Any previous TankGun implementation that accessed the Actual Tank Gun would be extracted as an abstract protected method declared in the abstract TankGun and defined and implemented in the new TankGunAdapter. TankGunAdapter would delegate to the Actual Tank Gun directly. TankGunTrainer would also have to define and implement the same abstract protected methods in TankGun, but TankGunTrainer would have no access the Actual Tank Gun. Its implementations could be empty NO-OP methods, or it could record the Actual Tank Gun request, much like a Spy would.

This design retains the bulk of the Tank Gun behavior implementation in TankGun. TankGunAdapter would fulfill the behavior with the Actual Tank Gun, whereas TankGunTrainer would not. This segregation will help ensure that the Actual Tank Gun will not be engaged while in TRAINER mode.

CONFIGURE

Mode resides within the TankGunWeaponSystem concrete factory. TankGunConfigurer has no concern about mode. Its single responsibility is to create the TankGunWeaponSystem and inject it into the tank instance of the CombatVehicle.

TankGunWeaponSystem creates the appropriate concrete TankGun and TankGunShell objects based upon ACTIVE or TRAINER modes.

This is the only place where mode is considered, which limits the scope of incorrect configurations. All possible mode values are considered in the code snippet. If a new mode is added, such as MAINTENANCE, then this method will throw an Exception. NOTE: Each concrete WeaponSystem will need to consider mode within its own context.

I listed this as the final design for now, because I’m still not 100% satisfied with it, but I’m not going to continue beyond this design in this blog.

A future consideration includes: How is the CombatVehicle tank updated when the mode changes?

I would consider using the Observer Design Pattern, which I have not presented yet (blog TBD):

Just to give a hint of what I’d do, I’d consider an Observer, probably in the CONFIGURE region, which would receive mode update notifications and make adjustments to the tank as needed.

I’m still not convinced that the fire() method works in all concrete occurrences of the abstraction. Does fire() accurately model a bow and arrow as well as an assault weapon machine gun?

Summary

Cohesive Abstraction occurs when multiple Abstractions have relationships that need to remain consistent. The Abstract Factory Design Pattern is one mechanism that helps maintain consistency.

One could argue that my designs are overengineered. That’s possible, but the design is modular with good dependency management. It can be modified without a complete redesign as has been shown throughout this blog as I’ve tweaked it.

A Cautionary Tale

Is consistency and all this effort really worth the effort? Let me close with this cautionary tale.

One of the products I worked on had a video feature where customers could request that their users upload a short video. Our product stored the video in the cloud with an external video vendor. The vendor would provide a URL to the video resource on their system, and we’d store that in a user/video database table.

One day multiple customers started contacting customer support stating that they couldn’t access any of the videos. Customer support confirmed the behavior, but they were unable to find a workaround.

Development got involved. It didn’t take us too long to realize that the user/video table was gone. Poof! Nada! Bupkis!

No. No. No. Oh God. This is really bad. This is really, really bad.

We confirmed that the videos still resided with the external vendor, but we didn’t know which video was associated with which user.

Database backups restored most of the missing user/video connections; however, the backups were either two weeks or two months old. This was years ago, and I don’t remember which. Regardless, the most recent and relevant user/video connections were still missing.

After some more investigation, we found a command in the video vendor’s API that returned the URL of our entire set of videos. Part of their URL contained our user ID that we had submitted when uploading the video. Because our vendor provided that really important nugget of information, only by chance, we were able to extract the user ID and reconstruct most of the user/video connections information. It wasn’t a complete restoration, since the table had additional context information. That information was completely gone and unrecoverable, but we were happy to give our customers the ability to view their user videos once more.

How did this happen? We never found the root cause. We think it was a comedy of errors. This is mostly speculation, but we think that it involved one of our user/video automated tests. The test manipulated the database. The test designer didn’t want to leave any test-created artifacts in the database after completion, since they could affect the next execution of the test. The test created a clean slate for each execution by deleting the user/video table upon completion. We think the automated test was executed against the production environment. When the test cleaned the test environment, it deleted the production table.

I thought I was going to lose my job. I had been recently repremanded for another vendor issue that affected customers. However, we were commended for finding the issue and resolving it well enough in a few hours. We took additional steps to prevent future disasters, such as removing developer and tester write-access to the production database.

The user/video feature wasn’t a major feature in our product. This could have a much worse disaster. Core business tables could have been deleted, and most of them would not have had an option for reconstruction.

This personal experience is the main reason why I feel that consistency is worth the effort.

References

Previous References

Comments

Previous: What Is Abstraction?

Home: Design Pattern Evangelist Blog