![]() |
Home | Articles | Talks | Links | Contact Me | ISA | ThoughtWorks |
A set of mappers to handle inheritance hierarchies
When you are mapping from an object-oriented inheritance hierarchy in memory to a relational database you need to minimize the amount of code needed to save and load the data to database. You also want to provide both abstract and concrete mapping behavior that allows you to save or load a superclass or a subclass.
Although the details of this behavior varies with your inheritance mapping scheme (Single Table Inheritance, Class Table Inheritance, Concrete Table Inheritance) the general structure works the same for all of them.
You can organize the mappers using a hierarchy so that each domain class has a mapper that saves and loads the data for that domain class. That way when the mapping changes, you have one point where you can change the mapping. This approach works well for concrete mappers that know how to map the concrete objects in the hierarchy. There are times, however, where you also need mappers for the abstract classes in the hierarchy. These can be implemented with mappers that are actually outside of the basic hierarchy but delegate to the appropriate concrete mappers.
To best understand how this works, I'll start with the concrete mappers. In the sketch the concrete mappers are the mappers for footballer, cricketer, and bowler. The basic behavior of the mappers include the find, insert, update, and delete operations.
The finder methods are declared on the concrete subclasses because they will return a concrete class, so the find method on BowlerMapper should return a Bowler not an abstract class. Common OO languages cannot let you change the declared subtype of a method, so it's not possible to inherit the find operation and still declare a specific return type. You can, of course, return an abstract type but that forces the user of the class to downcast - which is best to avoid. (A language with run time typing avoids this problem.)
The basic behavior of the find method is to find the appropriate row in the database, instantiate an object of the correct type (a decision that's made by the subclass) and then load the object with data from the database. The load method is implemented by each mapper in the hierarchy and the mapper loads the behavior for its corresponding domain object. So the bowler mapper's load method loads the data specific to the bowler class, calls the superclass method to load the data specific to the cricketer, which calls its superclass method, etc.
The insert and update methods operate in a similar way using a save method. Here you can define the interface on the superclass, indeed on a Layer Supertype. The insert method creates a new row and then saves the data from the domain object using the save hook methods. The update method just saves the data using the save hook methods. The save hook methods operate similarly to the load hook methods, with each class storing its specific data and calling the superclass save method.
This scheme makes it quite easy to write the appropriate mappers to save the specific information needed for a particular part of the hierarchy. The next step is to support loading and saving an abstract class, in this example a player. While a first thought is to put appropriate methods on the superclass mapper, that actually gets awkward because while concrete mapper classes can just use the abstract mapper's insert and update methods, the player mapper's insert and update needs to override these to call a concrete mapper instead. The result is one of those combinations of generalization and composition that twists your brain cells into an appalling knot.
So I prefer to separate them into two classes. The abstract player mapper is the one whose responsibility is to load and save the specific player data to the database. This is a class that is abstract and whose behavior is just used by the concrete mapper objects. A separate player mapper class is used for its interface. It provides a find method and overrides the insert and update methods. For all of these its responsibility is to figure out which concrete mapper should handle the task and delegate to it.
Although this broad scheme makes sense for each type of inheritance mapping, the details do vary with the exact mapping scheme, so it's not possible to show a code example for this case. You can find good examples in each of the inheritance mapping patterns: Single Table Inheritance, Class Table Inheritance, and Concrete Table Inheritance.
This general scheme makes sense for any inheritance based database mapping. The alternatives involve such things as duplicating superclass mapping code amongst the concrete mappers or folding the player's interface into the abstract player mapper class. The former is a heinous crime and latter is possible but leads to a messy and confusing player mapper class. So on the whole its hard to think of a good alternative to this pattern.
![]() | ![]() |