C++ FAQ Celebrating Twenty-One Years of the C++ FAQ!!!
(Click here for a personal note from Marshall Cline.)
Section 25:
[25.6] Is there a simple way to visualize all these tradeoffs?

Here are some of the "goodness criteria," that is, qualities you might want. In this description, N is the number of geographies and M is the number of power sources:

  • Grow Gracefully: Does the size of the code-base grow gracefully when you add a new geography or power source? If you add a new geography (going from N to N+1), do you need to add one new chunk of code (best), M new chunks of code (worst), or something in between?
  • Low Code Bulk: Is there a reasonably small amount of code bulk? This usually is proportional to ongoing maintenance cost — the more code the more cost, all other things being equal. It is also usually related to the "Grow Gracefully" criteria: in addition to the code bulk of the framework proper, best case there would be N+M chunks of code, worst case there would be N*M chunks of code.
  • Fine Grained Control: Do you have fine granular control over the algorithms and data structures? For example, do you have the option of having a different algorithm and/or data structure for any of the N*M possibilities, or are you stuck with using the same algorithm and/or data structure for all, say, gas powered vehicles?
  • Static Detect Bad Combos: Can you statically ("at compile time") detect and prevent invalid combinations. For example, suppose for the moment that there are no pedal-powered space vehicles. If someone tries to create a pedal-powered space vehicle, can that be detected at compile time (good), or do we need to detect it at run-time?
  • Polymorphic on Both Sides: Does it let users treat either base class polymorphically? In other words, can you create some user code f() that takes any and all, say, land vehicles (where you can add a new kind of land vehicle without requiring any changes to f()), and also create some other user code g() that takes any and all, say, gas powered vehicles (where you can add a new kind of gas powered vehicle without requiring any changes to g())?
  • Share Common Code: Does it let new combinations share common code from either side? For example, when you create a new kind of gas powered land vehicle, can that new class choose to optionally share code that is common to many gas-powered vehicles and choose to optionally share code is common to many land vehicles?

This matrix shows techologies as rows and "goodness criteria" as columns. Good means the row's technology has the column's goodness criteria, "no" means it does not.

  Grow Gracefully? Low Code Bulk? Fine Grained Control? Static Detect Bad Combos? Polymorphic on Both Sides? Share Common Code?
Bridge Good Good
(N+M chunks)
no no no Good
Nested generalization no no
(N*M chunks)
Good Good no no
Multiple inheritance no no
(N*M chunks)
Good Good Good Good

Important: do not be naive. Do not simply add up the number of Goods, choosing based on most good or least bad. THINK!!

  1. The first step is to think about whether your particular situation has other design options, that is, additional rows.
    • Recall that the "bridge" row is really a pair of rows — it has an assymetry that could go in either direction. In other words, one could put an Engine* in Vehicle or a Vehicle* in Engine (or both, or some other way to pair them up, such as a small object that contains just a Vehicle* and an Engine*).
    • Similar comments for the nested generalization row: it is actually a pair of rows because it also has an assymetry, and that assymetry gives you an extra option: you could first decompose by geography (land, water, etc.) or first by power source (gas, nuclear, etc.). These two orders yield two distinct designs with distinct tradeoffs.
  2. The second step in using the above matrix is to think about which column is most important for your particular situation. This will let you give a "weight" or "importance" to each column.
    • For example, in your particular situation, the amount of code that must get written (second column) may be more or less important than the fine grained control over algorithms/data structures. Do not get caught up trying to figure out which column is more important in some abstract, generic, one-size-fits-all view of the world, because one size does not fit all!!
    • Question: is code bulk (and therefore maintenance cost) more or less important than fine grained control? Answer: Yes, code bulk (and therefore maintenance cost) is either more or less important than fine grained control. That's a joke; lighten up.
    • But this part isn't a joke: don't trust anyone who thinks they know whether code bulk (and therefore maintenance cost) is always more or always less important than fine grained control. There's no way to know until you look at all the requirements and constraints on your particular situation! Far too many programmers think they know the answer before they are familiar with the situation. That's worse than dumb; it is unprofessional and dangerous. Their one-size-fits-all answer will sometimes be right. Their one-size-fits-all answer might have been right in every case they have ever seen in their limited range of experience. But if their past success blinds them from asking the tough questions in the future, they are a danger to your project and should get a thonk on the noggin ("thonk" and "noggin" are highly technical terms).

Your ultimate choice will be made by finding out which approach is best for your situation. One size does not fit all — do not expect the answer in one project to be the same as the answer in another project. Your past successes can become, if you are not careful, the seeds of your future failure. Just because "it" was best on your previous project does not mean "it" will be best on your next project.