A short guide to help your code go further
Keeping specific implementations at the edges of software is a well-known best practice in software development, and it usually works well as a heuristic for building powerful object-oriented structures and easy-to-maintain code.
The general idea refers to having classes know each other’s interfaces (eg, which operations could be invoked on each one) without knowing their specific implementation or “data type.” For example, which specific class or subclass an object belongs to).
Normally, this design practice is reached by means of defining software dependencies in terms of interfaces, thus leaving some dependency injection mechanism to figure out which specific implementations should be used throughout the execution thread.
Specific implementations are often defined at the beginning of the execution thread, at the very first layers of the software, before entering the core components. We refer to these places as the “edges of the software,” which conform to the actual entry point of every execution of it.
Having made a boring theoretical OOP introduction, I’ve faced a specific problematic pattern many times during my few years of experience working as a PHP developer. Here I describe it and provide some insights that helped me to deal with it in different scenarios while trying to stick to the above design practice.
The problematic pattern can be described as having a class that makes use of the result that another one provides. The result is defined in terms of a generic interface, so we may know some operations that can be invoked on the instance. However, the result also has some specific operations and data pieces that the resulting consumer wants to use.
The consumer should be able to work with different possible result instances by implementing different result processing algorithms, depending on each result’s type. How can we achieve this without spreading the implementation details through all the code?
I know this is a little tedious to understand, so let’s work on this with a concrete example using a Command design pattern setup:
Despite the number of files, this setup is easy to understand: there’s a
Command interface that returns a
CommandResult instance as the result of executing a specific command. We have an
ExecutionContext class that defines the general structure of a specific context.
A context would be responsible for handling (executing and processing its result) each command. We can see that each specific command result subclass holds different operations, but the
ExecutionContext class is only aware of the
CommandResult interface. How could we handle each command result type accordingly?
This has been my nemesis on many occasions. The lack of polymorphic type hinting and generics in PHP makes it difficult to work under these scenarios; unlike Java, which has good native solutions for cases like these. Anyway, I could identify three useful approaches that allowed me to address this problem successfully in the past:
- Type casting the generic result into the expected type
- Using the
- Restructuring components design
To be honest, this is my least preferred solution. Since we do know that behind a
CommandResult instance there will be a specific class instance, we could try to provide different implementations for each specific instance as follows:
processResult implementation asks which specific type the result has in order to handle it accordingly. Since each private
processCommandResultX method expects to receive a
CommandResultX instance as defined on its signature, our IDE can help us with autocompletion and type hinting within each of these methods.
processResult method could be defined directly in the base
ExecutionContext class, leaving the definition of each “
processCommandResultX” method to each subclass.
Moreover, the base class could provide default implementations for each specific method, along with a default implementation that would be executed in case no
instanceof statement catches a specific command result (though, we may want to throw an error in this case, warning us that we forgot to handle it).
A more powerful and flexible version would make use of the Chain of Responsibility pattern to define each specific processing as a chain steel. That way a steel could process only
CommandResultA instances, leaving all other result types to the following steels, and so on.
We could have steels either at the beginning or at the end of the chain that make extra processing for all types of results, for example, enabling data logging for each result no matter which type it is.
Although having many
instanceof checks and processing methods could be a little tricky and verbose, this first approach keeps the specific implementation code at the edges of the software, as long as we consider the execution context classes part of the edges. Adding a new command result type entails, therefore:
- Adding a new specific processing method in the
ExecutionContexthierarchy, and implementing it in the child classes
- Adding a new “
instanceof” check in the
- When using a Chain of Responsibility, adding a new chain steel and maybe modifying some existing ones
By means of the
Visitor design pattern, we can reach a more elegant setup of components, as shown below:
Here, we let each specific result control how it should be processed: each
ExecutionContext has a collection of specific processing methods. The specific result should choose the appropriate one by implementing the
Elegance is given by not having to check for each result’s class type using
instanceof statements. However, this approach is not so different from the previous one. Adding a new command along with a new command result type would entail:
- Adding a new
processCommandResultXmethod in the
ExecutionContextbase class, and implementing it on each concrete context subclass.
If we turn the theoretical mode on again, we could think that this problem is merely a design problem, and thus, this current setup doesn’t work well due to some misconceptions and assumptions that were made wrongly.
ExecutionContext was designed to process
CommandResult instances. But we’re trying to process
CommandResultB instances. Hence, we’re mixing two different levels of abstraction in the same class. At any point in the code, we should work at either a low level or a high level of abstraction, but not with both of them simultaneously.
CommandResultA defines a completely new method called
getString, which isn’t defined at the parent class level. The same happens with
CommandResultB. Moreover, both command result types aren’t interchangeable with each other.
We’re violating the Likskow’s Substitution Principle, and this comes from the assumption that
CommandResultB are kinds of
CommandResult, which in turn is not true since they have nothing in common. Neither share the same data fields nor have the same operations.
Noticing these design errors might bring up good insights to refactor and redesign the current solution. However, since I assume that two commands could return different data fields as their results, I’ll definitely want to handle them differently. Command result types vary along with the processing algorithms. Therefore, we could try to keep them both at the very edges of the software, as you can see below:
I’ve added a
ProcessingStrategy set of classes that define how each specific result type should be processed. The
ExecutionContext now is a core component and must be set as a processing strategy before sending it a command. We keep the result type processing at the system’s edges using this approach since they’re only defined in the
This approach supposes having one specific processing strategy per command result type, although the
processResult method expects a
CommandResult instance instead of a specific result type (like
As a consequence, I explicitly check that the passed-in result’s type is the one I expect to receive. I use two approaches:
ProcessingStrategyA casts the result to a
ProcessingStrategyB explicitly checks the type of the given result and relies on PHPDoc for autocompletion and type hinting.
Note that a processing strategy that handles both types of results could be provided, too. Thus, this is a more flexible approach than the others. Also note that if we pass an unexpected command result type to the execution context, it will throw up an error. A safer alternative would include some error handling and default behavioral mechanisms.
A type-relaxed of this approach would be one where the
ExecutionContext class uses a
processingStrategy without explicitly defining its expected type, which you can see below:
This version allows the
ExecutionContext to use anything that has a
processResult method as a valid processing strategy. By relaxing the type requirements, we can delete the inheritance of
ProcessingStrategythus explicitly saying that a
ProcessingStrategyA instance will only be able to process
CommandResultA instances, instead of
CommandResult ones. The main benefit here is that we get rid of
instanceof checks and type casting, and the restriction of how a processing strategy processes a result is made explicit.
Adding a new command result type with this layout would entail:
- Adding a new processing strategy that can deal with the new result type, though an existing one could be reused.
I hope this article was helpful. Thank you for reading, and stay tuned for more!