21 July 2021 by Phillip Johnston • Last updated 24 June 2025
Jerry points out that we commonly fail to differentiate between metaphors that are related to products, producers, and processors.
-
A product is an abstraction whose instances are created for use by client code
- For example, imagine a real-world manufacturer who produces bolts, radios, cars, or other products
- A client must obtain the product to use it
-
A producer is an abstraction responsible for creating a product
- For example, imagine a manufacturer or supplier in the real world
- A producer may create multiple products, but a product never creates itself or destroys itself
- In simple cases, client code may act as a producer by creating products directly. At other times, client code may use a separate producer abstraction (e.g., factory method or abstract factory) to create products
-
A processor is an abstraction that manipulates a product in some way.
- A processor might repair a product, rearrange it, store it, operate on it, or adjust it in various ways
- A processor never creates a product or vice versa.
- Likewise, a processor never creates a producer or vice versa.
As described above, we will often use products, processors, and producers together. However, in order to come up with a cohesive design that separates concerns, we need to make sure these abstractions are independent.
Jerry gives the example of a Message class that contains serialization and deserialization procedures:
- In operation, a blank
Message is instantiated
- Then its deserialization procedure is called, filling the
Message with values from the mainframe.
- Later, after the object has been modified, its serialization procedure is called to send all the
Message values to the mainframe.
This design mixes all three metaphors – the Message class is the product, producer, and processor. Instead we can improve the cohesiveness and reusability of this design by:
- Having the
Message class be only a product
- Creating a producer, e.g.
MessageDeserializer, which creates a Message instance
- Creating a processor, e.g.
MessageSerializer, which sends message values to the mainframe
As another example, consider a temperature sensor driver which reads the temperature and monitors for temperature swings and notifies objects when an unexpected swing occurs. This metaphor is mixed in the following ways:
- The driver produces temperature data points
- The driver processes these samples to identify unexpected swings
- The driver produces notifications when a swing occurs
The problem with mixing these metaphors is that now objects which shouldn’t need to know anything about the specific temperature sensor module are now coupled with it (in order to register for notifications). A design that does not use mixed metaphors would involve more components, but each component would be more independent, and changes in one would not necessarily require changes to the others. This might look like:
- A producer (
TemperatureDriver), which can send the product (TemperatureSample) to interested parties
- One such interested party can be the
TemperatureSwingMonitor that processes samples and monitors for temperature swings
- If a temperature swing is detected, the
TemperatureSwingMonitor uses a NotificationCenter (producer) to generate a TemperatureSwingDetected notification (product) and send it to registered listeners
By splitting up the metaphors, we can reduce coupling in the system, increase the reusability of the various components, and keep clients decoupled from both the TemperatureDriver and the TemperatureSwingMonitor.
Certainly, we will not always require the use of product, producer, or processor abstractions. We might see mixed metaphors in different ways. Keep the core lesson in mind: we can improve our designs by identifying mixed metaphors and splitting them up appropriately.
References