Can the Diamond Problem be really solved?
Asked Answered
S

18

18

A typical problem in OO programming is the diamond problem. I have parent class A with two sub-classes B and C. A has an abstract method, B and C implement it. Now I have a sub-class D, that inherits of B and C. The diamond problem is now, what implementation shall D use, the one of B or the one of C?

People claim Java knows no diamond problem. I can only have multiple inheritance with interfaces and since they have no implementation, I have no diamond problem. Is this really true? I don't think so. See below:

[removed vehicle example]

Is a diamond problem always the cause of bad class design and something neither programmer nor compiler needs to solve, because it shouldn't exist in the first place?


Update: Maybe my example was poorly chosen.

See this image

Diamond Problem
(source: suffolk.edu)

Of course you can make Person virtual in C++ and thus you will only have one instance of person in memory, but the real problem persists IMHO. How would you implement getDepartment() for GradTeachingFellow? Consider, he might be student in one department and teach in another one. So you can either return one department or the other one; there is no perfect solution to the problem and the fact that no implementation might be inherited (e.g. Student and Teacher could both be interfaces) doesn't seem to solve the problem to me.

Sauna answered 18/2, 2009 at 16:4 Comment(1)
That's more of a blog post than a question. Sadly, it seems that most of the "answers" here so far haven't read the entire thing. But it is an interesting perspective you present.Chenille
C
22

What you're seeing is how violations of the Liskov Substitution Principle make it really hard to have a working, logical object-oriented structure.
Basically, (public) inheritance should only narrow the purpose of the class, not extend it. In this case, by inheriting from two types of vehicles you are in fact extending the purpose, and as you noticed, it doesn't work - move should be very different for a water vehicle than for a road vehicle.
You could instead aggregate a water vehicle and a ground vehicle object in your amphibious vehicle and decide externally which of the two will be appropriate to the current situation.
Alternatively you could decide that the "vehicle" class is needlessly generic and you'll have separate interfaces for both. That doesn't solve the problem for your amphibious vehicle on its own though - if you call the movement method "move" in both interfaces, you'll still have trouble. So I'd suggest aggregation instead of inheritance.

Cinerator answered 18/2, 2009 at 16:14 Comment(5)
Good point. But doesn't multiple inheritance than always violates this principle as inheriting from two classes always makes the child class "wider"?Sauna
If the two interfaces are completely orthogonal (i.e. they don't apply to the same concept) then you can get away with it. BUT... then you violate the single responsibility principle, which is another world of hurt.Cinerator
"narrow the purpose of the class" is called specialization :)Astigmia
I disagree. An amphibious vehicle IS both a land and a water vehicle, it doesn't HAVE a land and a water vehicle. I can't see the example anymore, but the move method should specify a surface or something and that would make it work fine. If it doesn't, the vehicle interface should be improved.Irritant
Using "IS A" as mnemonic for inheritance is flawed, that's in fact the whole reason for paying particular attention to correct Liskov Substitution. The example often cited is of squares and rectangles with the area method, where it's not trivial at all to get correct Liskov Substitution.Cinerator
R
7

In your example, move() belongs to the Vehicle interface and defines the contract "going from point A to point B".

When GroundVehicle and WaterVehicle extend Vehicle, they implicitly inherit this contract (analogy: List.contains inherits its contract from Collection.contains -- imagine if it specified something different!).

So when the concrete AmphibianVehicle implements move(), the contract it really needs to respect is Vehicle's. There is a diamond, but the contract doesn't change whether you consider one side of the diamond or the other (or I would call that a design problem).

If you need the contract of "moving" to embody the notion of surface, don't define it in a type that doesn't model this notion:

public interface GroundVehicle extends Vehicle {
    void ride();
}
public interface WaterVehicle extends Vehicle {
    void sail();
}

(analogy: get(int)'s contract is defined by the List interface. It couldn't possibly be defined by Collection, as collections are not necessarily ordered)

Or refactor your generic interface to add the notion:

public interface Vehicle {
    void move(Surface s) throws UnsupportedSurfaceException;
}

The only problem I see when implementing multiple interfaces is when two methods from totally unrelated interfaces happen to collide:

public interface Vehicle {
    void move();
}
public interface GraphicalComponent {
    void move(); // move the graphical component on a screen
}
// Used in a graphical program to manage a fleet of vehicles:
public class Car implements Vehicle, GraphicalComponent {
    void move() {
        // ???
    }
}

But then that wouldn't be a diamond. More like an upside-down triangle.

Recife answered 18/2, 2009 at 18:1 Comment(2)
I like your example with Vehicle and GraphicalComponent - not a real diamond issue, but this is also one of the big problems you can get with interfaces, where the fact that no implementation is inherited actually does not save your day ;-)Sauna
Great answer! Although it would have been nice if it explicitly contained that the diamond problem is not really that big a problem and that allowing MI with interfaces does not really save much trouble.Irritant
O
6

C# has explicit interface implementation to partially deal with this. At least in the case where you've got one of the intermediate interfaces (an object thereof..)

However what probably happens is that the AmphibianVehicle object knows whether it is currently on water or land, and does the right thing.

Oulman answered 18/2, 2009 at 16:14 Comment(0)
I
5

People claim Java knows no diamond problem. I can only have multiple inheritance with interfaces and since they have no implementation, I have no diamond problem. Is this really true?

yes, because you control the implementation of the interface in D. The method signature is the same between both interfaces (B/C), and seeing as interfaces have no implementation - no problems.

Isobaric answered 18/2, 2009 at 16:12 Comment(4)
See above - just because move has no implementation does not solve the problem - I still cannot implement move() in the D class in such a way, that it will work.Sauna
Then let me rephrase: If you decide to implement a diamond pattern with interfaces, then it's your problem deciding HOW to implement it. There is, however, nothing implicitly wrong in doing it.Isobaric
By that logic, there's no diamond problem in MI, either: you just specify the order, if the default isn't desirable. This is just like SI+interfaces, except you don't have to write the dispatcher yourself.Chenille
it's still a diamond unless you can get to either implementation not always one or the other.Faddish
T
4

I don't know Java, but if Interfaces B and C inherit from Interface A, and class D implements Interfaces B and C, then class D just implements the move method once, and it is A.Move that it should implement. As you say, the compiler has no problem with this.

From the example you give regarding the AmphibianVehicle implementing GroundVehicle and WaterVehicle, this could easily be solved by storing a reference to Environment, for example, and exposing a Surface Property on Environment that the Move method of AmphibianVehicle would inspect. No need for this to be passed as a parameter.

You are right in the sense that it is something for the programmer to solve, but at least it compiles and should not be a 'problem'.

Tigress answered 18/2, 2009 at 16:13 Comment(0)
C
4

There is no Diamond Problem with Interface-based inheritance.

With Class-based inheritance, the multiple extended classes can have different implementation of a method, so there's ambiguity as to which method is actually used at runtime.

With Interface-based inheritance there's only one implemenation of the method, so there's no ambiguity.

EDIT: Actually, the same would apply to Class-based inheritance for methods declared as Abstract in the superclass.

Creedon answered 18/2, 2009 at 16:15 Comment(2)
For the compiler, but I, as programmer, must make up my mind how to implement it. So instead of having abstract classes one could say multiple inheritance is fine and if there is any ambiguity, neither implementation is inherited, the programmer must overwrite the method.Sauna
I think either you should be able to write the method such that it does the right thing in both cases, or you've made a mistake in trying to combine the classes in that way -- perhaps you need moveLand() and moveWater(), or some other approach.Shovelhead
O
4

The problem that you're seeing in the Student / Teacher example is simply that your data model is wrong, or at least insufficient.

The Student and Teacher classes are conflating two different concepts of "department" by using the same name for each of them. If you want to use this kind of inheritance, you should instead define something like "getTeachingDepartment" in Teacher and "getResearchDepartment" in Student. Your GradStudent, which is both a Teacher and a Student, implements both.

Of course, given the realities of graduate school, even this model is probably insufficient.

Obstacle answered 26/4, 2010 at 23:7 Comment(2)
You are probably right; the problem is just that interfaces like this tend to grow over the time and whoever created the Teacher interface might be a different person than the person who crated the Student interface, and neither one has ever considered that there might be a class having both interfaces at the same time. Maybe a third person created the GradTeachingFellow class and that before Student or Teacher had a department method at all.Sauna
What I'm trying to say here is that in an ideal OO language, different people make different classes and ideally no matter how the inheritance hierarchy looks like or how those classes are altered, nothing would ever break. This is far from reality, though. IMHO multiple inheritance only works great if: 1. there is inheritance beyond the scope of a library/binary; 2. everyone working on the library/binary has access to the source code and permission to change everything, if he likes. If either one is not true, it's better to stick with single inheritance (much less headache).Sauna
C
3

If I know have an AmphibianVehicle interface, that inherits of GroundVehicle and WaterVehicle, how would I implement it's move() method?

You would provide the implementation suitable for AmphibianVehicles.

If a GroundVehicle moves "differently" (i.e. takes different parameters than a WaterVehicle), then AmphibianVehicle inherits two different methods, one for on water, one for on the ground. If this is not possible, then AmphibianVehicle shouldn't inherit from GroundVehicle and WaterVehicle.

Is a diamond problem always the cause of bad class design and something neither programmer nor compiler needs to solve, because it shouldn't exist in the first place?

If it's due to bad class design, it is the programmer that needs to solve it, since the compiler wouldn't know how.

Czarism answered 18/2, 2009 at 16:21 Comment(0)
R
1

I don't think that preventing concrete multiple inheritance is moving the problem from the compiler to the programmer. In the example you gave it would still be necessary for the programmer to specify to the compiler which implementation to use. There is no way the compiler could guess which is correct.

For your amphibian class, you could add a method to determine if the vehicle is on water or land and use this decide on the move method to use. This will preserve the parameterless interface.

move()
{

  if (this.isOnLand())
  {
     this.moveLikeLandVehicle();
  }
  else
  {
    this.moveLikeWaterVehicle();
  }
}
Rabid answered 18/2, 2009 at 16:20 Comment(0)
D
1

In this case, it would probably be most advantageous to have AmphibiousVehicle be a subclass of Vehicle (sibling of WaterVehicle and LandVehicle), so as to completely avoid the problem in the first place. It would probably be more correct anyway, since an amphibious vehicle isn't a water vehicle or a land vehicle, it's a different thing altogether.

Digitiform answered 18/2, 2009 at 16:30 Comment(4)
While this is a good solution, however it means you must rewrite all the code you wrote for the other two child classes that would actually work quite well for AmphibiousVehicle as well... good solution, but destroys the idea behind implementation inheritance somewhatSauna
Only if a lot of the functionality in the amphibious vehicle is the same as the water vehicle and land vehicle. If something inherits from 2 objects that are almost complete opposites, most of it would probably have to be rewritten anyway.Digitiform
I think semantically it's most correct to do it the Diamond way. This is especially clear if WaterVehicle and LandVehicle implement some methods not directly inherited from Vehicle (like isSinking or inTrafficJam). AmphibiousVehicle should be able to do both.Irritant
Actually, LandVehicle would be able to implement isSinking also, if you want to get specific about it.Digitiform
F
1

If move() has semantic differences based on it being Ground or Water (rather than GroundVehicle and WaterVehicle interfaces both themselves extending GeneralVehicle interface that has the move() signature) but it is expected that you will mix and match ground and water implementers then your example one is really one of a poorly designed api.

The real issue is when the name collision is, effectively, accidental. for example (very synthetic):

interface Destructible
{
    void Wear();
    void Rip();
}

interface Garment
{
    void Wear();
    void Disrobe();
}

If you have a Jacket which you wish to be both a garment, and destructible you will have a name collision on the (legitimately named) wear method.

Java has no solution for this (the same is true for several other statically typed languages). Dynamic programming languages will have a similar issue, even without the diamond or inheritance, it's just a name collision (an inherent potential issue with Duck Typing).

.Net has the concept of explicit interface implementations whereby a class can define two methods of the same name and signature so long as both are marked to two different interfaces. The determination of the relevant method to call is based on compile time known interface of the variable (or if by reflection by the explicit choice of the callee)

That reasonable, likely name collisions are so hard to come by and that java has not been pilloried as unusable for not providing the explicit interface implementations would suggest that the problem is not a significant one for real world use.

Faddish answered 18/2, 2009 at 19:33 Comment(3)
Java does not have a problem with this. A java class that implements Destructible and Garment will have 3 methods, Wear(), Rip(), and Disrobe(). The fact that "wear" can have 2 meanings in English is a name problem, not a Java problem. Wear() will do whatever you code it to do, regardless of name.Creedon
Exactly, but wear cannot determine whether it was being invoked in the context of being asked to be worn or to degrade itself. This despite that fact that such information could have been made available to the type system at compile. Thus it cannot effectively do both.Faddish
Conflicting desires due to a name collision which cannot be resolved is the diamond problem. If you can include information to resolve it it stops being a problem and by definition is not a diamond anymoreFaddish
R
0

I realize that this is a specific instance, not a general solution, but it sounds like you need an additional system to determine state and decide on which kind of move() the vehicle would perform.

It seems that in the case of an amphibious vehicle, the caller (let's say "throttle") would have no idea of the state of the water/ground, but an intermediate determining object like "transmission" in conjunction with "traction control" might figure it out, then call move() with the proper parameter move(wheels) or move(prop).

Responsory answered 18/2, 2009 at 16:14 Comment(0)
M
0

The problem really exists. In the sample the AmphibianVehicle-Class need another information - the surface. My prefered solution is to add a getter/setter method on the AmpibianVehicle class to change the surface member (enumeration). The implementation could now do the right thing and the class stay encapsulated.

Munsey answered 18/2, 2009 at 16:16 Comment(0)
S
0

You can have the diamond problem in C++ (that allows multiple inheritance), but not in Java or in C#. There is no way for inheriting from two classes. Implement two interfaces with the same method declaration doesn't implies in this situation, since the concrete method implementation can only be made at the class.

Sawyers answered 18/2, 2009 at 16:23 Comment(4)
The diamond problem still exists to a certain extent in java for importing libraries. It is partially solved by full name qualification, but theres the possibility that code you have already written using class X could be made ambiguous by importing another library that defines a class X.Stroh
@BT I actually want to see such java code. When I say something like this, lot of people think I am mad. Can you share some code on git repo or here.Intitule
@Intitule I don't have a good example. I have certainly personally run into such cases tho, in the distant past when I still worked with java regularly. It should be pretty clear to any good programmer that unmanaged namespaces are incredibly likely to produce frustrating conflicts every once in a while. Without some central way to determine who gets what name, you'll eventually run into two things that use the same name and will be forced to do some annoying hacks.Stroh
@BT okk, it has been so long. I saw you are working as a Javascript developer now.Intitule
B
0

The diamond problem in C++ is already solved: use virtual inheritance. Or better yet, don't be lazy and inherit when it's not necessary (or unavoidable). As for the example you gave, this could be solved by redefining what it means to be capable of driving on the ground or in the water. Does the ability to move through water really define a water-based vehicle or is just something the vehicle is able to do? I'd rather think that the move() function you described has some sort of logic behind it that asks "where am I and can I actually move here?" The equivalent of a bool canMove() function that depends on the current state and the inherent abilities of the vehicle. And you don't need multiple inheritance to solve that problem. Just use a mixin that answers the question in different ways depending on what's possible and takes the superclass as a template parameter so the virtual canMove function will be visible through the inheritance chain.

Bonaparte answered 18/2, 2009 at 20:13 Comment(0)
S
0

Actually, if Student and Teacher are both interfaces, it does in fact solve your problem. If they are interfaces, then getDepartment is simply a method that must appear in your GradTeachingFellow class. The fact that both the Student and Teacher interfaces enforce that interface isn't a conflict at all. Implementing getDepartment in your GradTeachingFellow class would satify both interfaces without any diamond problem.

BUT, as pointed out in a comment, this doesn't solve the problem of a GradStudent teaching/being a TA in one department, and being a student in another. Encapsulation is probably what you want here:

public class Student {
  String getDepartment() {
    return "Economics";
  }
}

public class Teacher {
  String getDepartment() {
    return "Computer Engineering";
  }
}

public class GradStudent {
  Student learning;
  Teacher teaching;

  public String getDepartment() {
    return leraning.getDepartment()+" and "+teaching.getDepartment(); // or some such
  }

  public String getLearningDepartment() {
    return leraning.getDepartment();
  }

  public String getTeachingDepartment() {
    return teaching.getDepartment();
  }
}

It doesn't matter than a GradStudent doesn't conceptually "have" a teacher and student - encapsulation is still the way to go.

Stroh answered 26/4, 2010 at 7:49 Comment(2)
And how does that solve the problem, that GradTeachingFellow may be student in one department, but teacher in another one? Even if it is an interface, it will fail, just not at the code level.Sauna
You're right, it solves the problem at the code level, but not at the semantic level. In the case you're talking about, inheritance may not be what you want here - maybe you want encapsulation. This wouldn't work in Java, but alternatively, it would be semantically possible to inherit both methods under aliased names - e.g. getTeachingDepartment() and getStudentDepartment(). Java isn't a very flexible language, so encapsulation is probably your best bet for a GradStudent class.Stroh
J
0

interface A { void add(); }

interface B extends A { void add(); }

interface C extends A { void add(); }

class D implements B,C {

}

Isn't it diamond problem.

Jubilate answered 7/2, 2011 at 7:35 Comment(1)
Nope, because those are interfaces.Calchas
V
0

Composition over Inheritance

(Far too many answers, but since this didn't come up yet)

Generally these type of problem and most prominently the "deadly diamond of death" are solved/bypassed by employing composition (class a has x, y, z) over inheritance (class a is x, y, z).

Is it Solved / Pertaining to What?

  • You are correct, concerning your design this does not solve the problem of multi-inheritance. A language which was capable of "relabeling" base methods for a subclass (or sub iface) might solve such (at least for direct calls, when down-casting this would still remain problematic; see other attempts further down).
  • Concerning the (Java) language this is actually solved. It is clear to the compiler (and even the developer) that both interface methods (of same name and signature) will only be implemented via a single method.

Further Practical Thoughts

Nonetheless, either cases are solved when employing composition, which may however be a little inconvenient e.g. in Java, as (there) you can not directly use mySpecificGradTeachingStudent.getName().

  • Several languages (e.g. Go) require a base type if ambiguous (similar to this.Student.getDepartment), which goes - from way of thought - already in the direction of composition (over inheritance).

  • Similarly, other languages (e.g. Rust) go a step further and use what is called traits instead (completly banishing inheritance). Meaning a struct implements TraitA and TraitB, insead of "being A and/or B".

  • As all of these ideas basically build upon "interface concepts" and (simplified) adding syntactic sugar to composition (which imho is really the way to go, and solves other issues as well, e.g. objects completely changing after several layers of inheritance), here a last (very) different attempt. Any (multi-inheritance) language could simply add the concept of precedence. Meaning, (e.g.) the first base component/class which is implemented, will be the one supplying its method stack (i.e. Student) and the other methods are simply ignored. This still throws up questions like: "What happens when being specifically downcast to a Teacher object, but even these cases could be decited by convention. (Caution: This might not be the most intuitive solution from a design standpoint, but nonetheless a simple one for a language spec.)

Lastly, a ref of some of these thoughts for the Student/Teacher example (with minor deviations like dragons): https://dev.to/thisismahmoud/composition-over-inheritance-4fn9

Vercingetorix answered 2/7, 2021 at 10:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.