Refactoring tip: replace context-property with deferred provider method.
Posted by Marc Vangrieken on November 28, 2007
Here’s a tiny refactoring pattern that I have applied recently and will apply more frequently in the future. I actually didn’t refactor, I created the second structure straight away. Later I started to think about why I did it like that. The problem statement was something like this:
Create a class that can be used as a lookup table. We don’t know anything about the underlying data structure that will be containing the lookup table data and we don’t know when and from where we will retrieve that data. There will be more than one lookup table; different tables will be used in different semantic contexts of the application. Every one of these lookup tables can have a different type of key or a different structure.
The interface and the lookup algorithm can be generic; the only thing that will vary is the actual lookup table data. The most common way to implement a class to fulfill these requirements would look like the class on the left-hand side. It has one property, being the lookup data container. Depending on the context of the application, you can assign the one you need. I didn’t solve it this way though.
I created an abstract base class that contains the generic lookup algorithm and some other default implementations (e.g. validate the lookup data). I replaced the previous LookupData property with an abstract method that returns the lookup data. This abstract method has to be implemented via subclasses. We’ll end up with one subclass per lookup table, thus one subclass per semantic context of the application. All of these subclasses will have a name matching the context they are used in.
When I started thinking about why I had chosen to use the latter implementation I couldn’t find a good reason to refactor it to the first version.
Version 1
Pro:
- Fewer classes in the solution
Con:
- If the context-property is set using more than just 1 assignment, it will lead to code duplication in the client code
- When it turns out that non-generic behavior has to be added, developers will be tempted to start using extra properties/parameters with different values depending on the context of the application
Version 2
Pro:
- Client code developers only have to know which class to instantiate. After the constructor, everything is ready to be used
- Non-generic behavior and default implementations can be added easily
- Fewer chances for code duplication
- The object has become immutable now, which generally makes it more robust
- Because the object is immutable, it is guaranteed to be thread-safe
- It’s somewhat easier to derive mock objects for unit-testing
Con:
- More classes in the solution
- To keep the naming convention working, you’ll need a subclass for every semantic context of the application. In case you want to reuse exactly the same subclass in 2 different contexts, you will need to make some kind of type alias in order to respect the naming convention