问题
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
(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.
回答1:
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.
回答2:
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.
回答3:
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.
回答4:
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.
回答5:
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'.
回答6:
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.
回答7:
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 AmphibianVehicle
s.
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.
回答8:
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.
回答9:
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();
}
}
回答10:
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.
回答11:
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.
回答12:
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).
回答13:
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.
回答14:
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.
回答15:
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.
回答16:
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.
回答17:
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.
来源:https://stackoverflow.com/questions/561729/can-the-diamond-problem-be-really-solved