5

I would like to deepen the topic of multiple inheritance using Python and I usually find examples that are too simple. I love art and I imagined the following problem and I want to understand if multiple inheritance would be a good design solution for it.

Let's suppose we have STL files (meshes) of 3D scanned faces of ancient Greek/Roman sculptures. You can download this kind of files from websites like, for instance, SketchFab.

Let's suppose we can subdivide our meshes into three main categories:

  • Complete face, i.e. a face having both eyes and ears, nose, mouth and forehead.
  • "Upper" face, i.e. an incomplete/damaged face having both eyes and ears, the upper part of the nose and forehead, so there is no mouth.
  • "Lower" face, i.e. an incomplete/damaged face having only the mouth and the lower part of the nose, so no eyes, no forehead in this case.

Other categories might exist, for example, Left face or Right face or we can have just an eye. However, for the sake of simplicity, I would like to consider only the three categories listed above.

Now, we want to compute geometrical quantities for each face like, for instance, eye width, nose length, mouth width, etc. So we need to implement a bunch of methods.

Let's take a look at the table below. Again, for the sake of simplicity, I am using generic names for the methods.

enter image description here

Both the CompleteFace and UpperFace objects have method_a and both objects implement method_a in the same way. Maybe method_a could represent the eye width. The same logic applies to method_b and method_c. The LowerFace object does not have these methods.

Similarly, both CompleteFace and UpperFace have method_d that represents the same geometrical quantity, however the two objects implement this method using a different algorithm.

method_h is common to all of the three objects and the method is implemented in same way. For instance, this method allows to compute the area of the mesh by summing the area of the triangles composing the meshes.

method_i and method_j are methods implemented only for the CompleteFace object.

The remaining methods (method_k, method_l, etc.) are implemented in the same way by the CompleteFace and LowerFace objects but are not implemented for the UpperFace object.

The question is: what could it be the best design solution for this problem?

Perhaps, multiple inheritance could be used. We create the UpperFace class and the LowerFace class. Then, we create the CompleteFace class which inherits both the UpperFace and LowerFace classes. So, the CompleteFace class inherits method_a, method_b and method_c and there is no need to modify/override them. While method_d needs to be overridden. Finally, method_i and method_j are added. This seems to be a good solution, however multiple inheritance is usually considered a dangerous tool, because of problems like the diamond problem.

Is multiple inheritance the right choice for this problem or should be avoided in favor of other solutions based, for instance, on composition?

6
  • 3
    Does this answer your question? Why should I prefer composition over inheritance?
    – gnat
    Commented Dec 26, 2021 at 16:32
  • 1
    @gnat thanks! interesting answer, maybe too general. I am looking for an answer related to this specific example and related to the Python programming language. There a lot of articles about inheritance in Python, but they often show examples that, in my opinion, are too simple, and that's ok because these are examples tailored for beginners. I think this problem of modelling faces of sculptures it's a little bit more interesting and closer to real applications than the ubiquitous Dog and Car classes.
    – blunova
    Commented Dec 26, 2021 at 20:57
  • 6
    in one of past jobs I had problem more interesting and in totally real application of inheritance (to various kinds of financial investments orders). It was very difficult to maintain and the only thing that made it manageable and understandable was when I got rid of all the inheritance and replaced it with composition. It went exactly as in these examples about dogs and cars. These examples maybe only fail to convey how really bad it is to have this inheritance mess in a non-simplified case involving software maintenance and how much improvement is brought by using composition instead of it
    – gnat
    Commented Dec 26, 2021 at 21:15
  • 2
    Multiple inheritance is generally a bad thing, there's a reason a lot of modern languages don't even include it (such as C#). It's pretty rare that an object actually is two things simultaneously that aren't subsets of the other Commented Dec 27, 2021 at 18:32
  • 1
    Just a comment to provide some context to better understand the answers/suggestions: in order to solve this problem, it's not enough to only look at which method appears where (the table you made is a helpful step, but it's not enough). You have to consider what the methods mean, and the way they are going to be used by the calling code (i.e., what the calling code would actually look like). Which calls are external, which are internal? Are there groups of methods that are going to be called together? Things like that. Then decide on the interfaces/inheritance/composition based on that. Commented Dec 27, 2021 at 18:39

2 Answers 2

12

I would heavily recommend against using inheritance, this will sooner or later bring you into such kind of trouble mentioned by gnat in his comment. Instead, the very first thing I would recommend here is to design all the methods in a way they make sense for all types of faces.

This may result in certain methods simply returning zero (for example, the eye width for a face with no eyes could simply be zero). If zero isn't a good fit, use NaN, or return an error value, or throw an exception, whatever fits best.

When you are implementing these methods (and give them better names, not just method_a, method_b, ...), you may find out some of them are operating just on certain subdata, for which it may make sense to introduce certain classes on their own. For the sake of this example, lets assume own classes for Mouth, Eye or Nose to be such candidates. Or maybe it is better to have a class FacePart, or maybe that could be a base class for the former: I don't know what fits best for your case, but you get the idea. Now composition comes in: the Face object will contain certain FaceParts and delegate parts of the calculation to those.

How to design these ideas exactly is nothing I would decide up-front here. Better start implementing the methods and refactor whenever you find commonalities or occasions to extract the code into smaller units.

3
  • 4
    To add to that second paragraph, if you don't have a very good (design) reason for returning zero/NaN, then don't return zero/NaN: throw an exception 'by default' so that you are forced to acknowledge it if you use it by mistake. Zeros and NaNs may propagate making it hard to work out what went wrong if that code persists: the exception will force you to confront the design as soon as it's a problem, and give you a lovely stack-trace to identify why it has happened. Commented Dec 27, 2021 at 12:25
  • 3
    Better start implementing the methods and refactor whenever you find commonalities or occasions to extract the code into smaller units I second that. And Python makes it easy to work in such a way. Depending on what kind of software you are writing, even working with modules and methods first is a good approach and refactor to classes later. Commented Dec 27, 2021 at 13:42
  • 1
    @VisualMelon: the OP mentioned mostly examples where the methods return visual quantities. Returning zero when a certain face part isn't there will be pretty natural then.
    – Doc Brown
    Commented Dec 27, 2021 at 17:24
9

Multiple inheritance is a dangerous tool, like a chainsaw is a dangerous tool. When you know what you are doing, you can safely use it.

Your example of calculating metrics from scan of potentially damaged sculptures seems to be tailored to suggest multiple inheritance might be a solution. On the other hand, it does not account for the possibility that a left or right half of a face might be missing. Or just the nose or a single eye only.

For calculating geometric properties from potentially damaged sculptures, I would use just a single class and document for each method that it can also return or signal "cannot compute this property from the input data". No inheritance needed on the interface class(es).

3
  • Thanks for your answer! You are right about the possibility of having also the left/right half of the faces, so I have edited my question. I understand your solution, especially for methods like method_i and method_j. However, what about methods like method_d? Would you simply use an if...else statement?
    – blunova
    Commented Dec 26, 2021 at 21:14
  • 7
    The issue here is that these are not different types of faces, but different attributes of the same face that might or might not exist in a large number of possible combinations. By using multiple inheritance, you limit it to only one attribute, or you end up writing a class for each possible combination, which can explode quickly.
    – user10489
    Commented Dec 27, 2021 at 5:40
  • 1
    @blunova, For method_d, I would first try the calculation for a full face and if that fails, try again for an upper face. Commented Dec 27, 2021 at 7:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.