A not-so-generic construct
Mar 31 2008 ยท Application Design
Tricky software engineering problem. I’ll abstract it and explain the situation.
(fyi, a lot of this is just me thinking out loud, I don’t distinguish between type and instance as I think it’s clear from context and I use the term “flavor” to refer to objects which share a common base)
We have a certain object (ObjectA) that may contain an instance of another object (ObjectB). However, the constructor for ObjectB needs information about its “holder” (ObjectA), so the constructor for ObjectB must accept a reference to ObjectA.
Simple enough to use: create ObjectA; create ObjectB passing in ObjectA reference to constructor; call method to assign ObjectB to member of ObjectA.
However, there are many flavors of ObjectB (a plug-in architecture, with ObjectB as an abstract base class), so we have ObjectB1, ObjectB2, ObjectB3, etc.
Still no problem, we just create the flavor of ObjectB. As they are all derived from the same base class, we can pass them around generically (i.e. no problems with assignment to member of ObjectA).
We also want a single function (FunctionY) for creating our instances of ObjectA, that will automatically create and assign any flavor of ObjectB to ObjectA.
Now we run into a problem. Realize that we can’t pass a flavor of ObjectB as an argument of FunctionY (creating an instance requires ObjectA, which doesn’t exist as yet). So, how do we pass a flavor of ObjectB as an argument of FunctionY?
A simple “solution” is to change the problem and instead of passing ObjectA into the constructors of ObjectB flavors, have some sort of method (Init or whatever) that takes an ObjectA reference and can deal with the necessary ObjectA information after our ObjectB flavor has been constructed (i.e. pass ObjectA to ObjectB flavor after ObjectB flavor has been constructed). This works, but the problem it presents is that the ObjectA information may be important (perhaps even critical to construction), and having a lax policy such as this can lead to cases where ObjectB never receives its instance of ObjectA. So, the code becomes more error-prone. However, despite being more error-prone, this may work in a lot of cases as long as you can wrangle away the ObjectA-specific code from the constructor.
Of course, if we resolve to change the problem, we can also get away with having multiple functions. Or mangle things in any which way we want to get the square peg to fit the circular hole. However, I assumed I had a legitimate problem and decided to look for a solution.
Another simple but, this time, real solution is to create an ObjectB factory (woot! design pattern!) and pass some sort of representation (e.g. enum) for the flavor of ObjectB we want. This representation is passed to FunctionY, which will query the factory and get an instance of the flavor of ObjectB we want. This will work, but I don’t think it’s an elegant solution. We’re creating another construct to facilitate something which should be trivial (in my opinion, at least). We’re also increasing our maintenance burden; every time we create another flavor of ObjectB we need to update the factory and update the list of flavor representations.
I thought about this for a while and realized the central issue here was how to pass type information between functions.
So, I tried a solution involving reflection (this was in C#). Reflection works, as you can dynamically pass the type and dynamically invoke a constructor for the type. However, I don’t particularly like the reflection solution. There’s, of course, the high performance cost, but also the solution was too generic as your making assumptions about what parameters the constructor of a type will take and, as as it’s all dynamic, there’s no compile-time type checking. However, that being said, the bottom line is that a reflection solution works.
Hunting for another solution, I decided to experiment with generics. The idea was to create a builder class that took the flavor of ObjectB we wanted as its generic-type parameter. There would be a public method, BuildIt(…), which would take an instance of ObjectA and would simply instantiate the flavor of ObjectB we wanted using the passed-in parameter as its constructor argument. This would work, expect for the fact that generics don’t allow you to instantiate an object of the generic-type with constructor arguments. I quickly discovered the…
Cannot create an instance of the variable type ‘T’ because it does not have the new() constrainterror message spit out by the compiler. Damn.
If this was C++, templates should free us from this problem, but porting all the code over to another language isn’t exactly a feasible solution, to say the least.
In the end, I’m just kind of bummed out that I could find a solution I was really happy with. I’m sticking with the reflection solution, as this isn’t performance-critical code and I simply have to move on to other stuff.
Finally, looking at things from a wider perspective, just two things that seem weird. First, why can’t types be passed around and used, but resolved statically (by a compiler or a pre-processor). Like how #define is used in C I guess. Also, languages with generics/templates peg themselves as object-oriented, but when dealing with types in a generic fashion they’re completely agnostic to any form of inheritance and, as such, the object’s class hierarchy.