Design Pattern Evangelist Blog

Smart pointers about software design

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. In doing so, they depend upon and have knowledge of those other Abstractions. We want to ensure that that the concrete implementations for these Abstractions are resolved consistently.

Cohesion and Coupling

Cohesion and Coupling are related and somewhat confusing terms. Before I describe the consistent management of Cohesive Abstractions, make sure you understand Cohesion and Coupling. The two previous blogs focus upon them:

Cohesive Abstractions

Cohesive Abstractions are when coupled elements are abstractions, such as interfaces. Our goal is to ensure that the concrete classes implementing the abstractions are consistent in their given context. 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 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 paired 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 loaded Projectile were a Bullet or Arrow.

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

Abstract Factory Design Pattern

One way to ensure consistent 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 returns concrete reference to an abstract definition. The concrete classes that implement the Abstract Factory must implement each of those Factory methods and return concrete references for each, such that those concrete references are consistent. Do you see what I mean about Abstract Factory being challenging as the first pattern presented 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 as a weapon system does not replace or invalidate the Rifle weapon system. I did not show the previous Rifle related classes due to space constraints. 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 while flying to their destination.

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. The system included a TRAINER mode so that the guns would not actually fire the shells. I didn’t work on the TRAINER feature directly; therefore, what I describe is mostly hypothetical.

Code like this was 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 behavior, but given other code I experienced on the project, I feared it might look something like the design below, where an if statement would check the mode and then only fire the TankGun when mode is not TRAINER.

Safety Critical Tank Design

This looks like a reasonable choice, but 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 was presisted only in the user/video table. 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 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: Coupling and Cohesion – Take 2

Home: Design Pattern Evangelist Blog