Working around no interface inheritance - Forum - OpenEdge Development - Progress Community

Working around no interface inheritance

 Forum

Working around no interface inheritance

  • Hi all,

    One of the most important problems that PSC needs to address with the OOABL is the fact that there is no support for interface inheritance. I know this has been discussed before and I have complete faith in PSC that they will ultimately add this to the language. So let's avoid making this thread a discussion about the fact that there is no interface inheritance. What I am really looking for is opinions and advice on working around the problem.

    In Java and C# you can have an interface that inherits from another interface. This is very common and required behavior as you enforce interface contracts.

    As an example, I have an interface called Collection. All collections can have an iterator. They should therefore all inherit the Iterable interface. Collection is really an interface, because its implementation is going to differ depending on whether you are dealing with a List or a Map.

    Now... In C++ there is no support for interfaces. If you want an interface, you declare an abstract class with abstract methods and then inherit from that. In C++, though, there is support for multiple inheritance. In other words, class3 can inherit from both class1 and class2.

    The difference between an abstract class and an interface is that an interface simply defines the methods that must be there, whereas an abstract class can define standard behavior so that the sub-classes only have to implement the specialization.

    When I am coding in Java or C# and I am writing code that I intend to be generic, the rule that I have is that I always program against the interface, not the class. The reason is that the interface is the contract that the specialization has agreed to abide by.

    In C++, you have no alternative but to program against the abstract classes, but I have a rule that I have abstract classes with no method implementation that start with an "I" that behave exactly as interfaces, so I am effectively programming against interfaces anyway.

    The lack of interface inheritance in OOABL presents an interesting dilemma. I really need ICollection to inherit IIterable. So I am left with having to create an abstract class called ICollection that implements IIterable as abstract methods. I then also have a Collection class that is an abstract class that contains the implementation of the standard Collection class behavior.

    I don't like this, but I think it works. The way I am thinking about it is that ICollection will become an interface when PSC provides interface inheritance in the language in 10.2BSP1 (no pressure :)).

    Does anyone have any opinion or suggestion about this? Any comments? Am I missing something?

  • In C++, you have no alternative but to program against the abstract

    classes, but I have a rule that I have abstract classes with no

    method implementation that start with an "I" that behave exactly as

    interfaces, so I am effectively programming against interfaces

    anyway.

    One important difference between C++ and ABL is that C++ allows multiple inheritance, so your class will inherit from the 'interface' and from an implementing class. In ABL there are separate keywords to distinguish between these (INHERITS and IMPLEMENTS). So you're going to have to revisit your code anyway, if you want to use real interfaces if and when interface inheritance appears.

    I don't like this, but I think it works. The way I am thinking about

    it is that ICollection will become an interface when PSC provides

    interface inheritance in the language in 10.2BSP1 (no pressure ).

    >

    My preferred approach is for the implementing class to specify both/all interfaces, and then CAST() a lot (how much depends, of course, on the depth of the hierarchy). This way, you only need to change your interface definitions. Refactoring existing classes is optional, and new classes can take advantage of the inheritance.

    -- peter

  • Given that a class can implement multiple interfaces, why is it not sufficient to define separate interfaces and implement all of them?  I understand that there is a certain tidiness, if one interface is actually an extension of another interface, but here it seems like the two interfaces are actually different spheres of responsibility and thus appropriately separate.  Even in the case of a true extension, partitioning into the base and the extension parts is pretty clean since one can implement eithre just the base or the base and the extension.  The only odd part is that one can't implement the extension on its own.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • One important difference between C++ and ABL is that C++ allows multiple inheritance, so your class will inherit from the 'interface' and from an implementing class. In ABL there are separate keywords to  distinguish between these (INHERITS and IMPLEMENTS). So you're going to have to revisit your code anyway, if you want to use real interfaces if and when interface inheritance appears.

    I'm not sure I understand the issue you are raising. I get that I would have to go back and change the IMPLEMENTS in the abstract class to an INHERITS, but I would be changing the abstract class to an interface anyway.

    My preferred approach is for the implementing class to specify both/all interfaces, and then CAST() a lot (how much depends, of course, on the depth of the hierarchy). This way, you only need to change your interface definitions. Refactoring existing classes is optional, and new classes can take advantage of the inheritance.

    I thought about this, but it violates the rules of encapsulation. An ICollection is an IIterable. A Map is an ICollection. Map only implements IIterable because ICollection enforces it. That should be reflected in the OO model.

  • As I said in my other post, this violates the rules of encapsulation.

    Where this gets really ugly is when you have multiple interfaces that represent an object of a certain type, and you want to create a set of objects that all have the same set of interfaces. The only way to do this is through interface inheritance or abstract classes.

    The truth is, as a C++ programmer, I really don't care much about the distinction between abstract classes and interfaces. As a Java/C# programmer, I need multiple inheritance (implementation). OOABL does not support multiple inheritance and it does not support interface inheritance.

    In C#, this would be a very big problem because runtime class loading needs interfaces, but I consider that a flaw in C#, rather than in OO.

  • bsgruenba wrote:

    One important difference between C++ and ABL is that C++ allows multiple inheritance, so your class will inherit from the 'interface' and from an implementing class. In ABL there are separate keywords to  distinguish between these (INHERITS and IMPLEMENTS). So you're going to have to revisit your code anyway, if you want to use real interfaces if and when interface inheritance appears.

    I'm not sure I understand the issue you are raising. I get that I would have to go back and change the IMPLEMENTS in the abstract class to an INHERITS, but I would be changing the abstract class to an interface anyway.

    It's mainly the question of the amount of refactoring you need to do. In the other case, you don't have to change anything except add the " INHERITS IParentInterface " clause to your existing interface. The implementing classes wouldn't need to change at all.

    I thought about this, but it violates the rules of encapsulation. An ICollection is an IIterable. A Map is an ICollection. Map only implements IIterable because ICollection enforces it. That should be reflected in the OO model.

    Possibly, although the theoretical model is still valid. It's the implementation that's wonky (or however you care to describe it).

    -- peter

  • Well, let's start by recognizing that you aren't going to get what you really want here, at least not until some future release, so whatever you do is going to be some kind of compromise .... and compromises generally involve not getting exactly what you wanted.

    But, let's make this a bit more concrete so that we are sure that we are talking about the same thing.  Let's suppose we have an interface Base which defines A, B, and C and that what you want is an interface BasePlus which inherits Base and defines D, E, F.  My suggestion is to define interface Plus which defines D, E, and F.  Then, in the class which provides the implementation, you implement Base and Plus instead of implementing BasePlus.  This seems to me to provide a clean separation and when you get what you want, the refactoring is to change Plus to BasePlus and convert the implementing class to implement the single interface instead of both.

    I see this as a compromise in elegance, but preserving clear separation of responsibility since Plus is not pretending to be BasePlus, but rather is defining a separate set of functionality.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • It's mainly the question of the amount of refactoring you need to do. In the other case, you don't have to change anything except add the " INHERITS IParentInterface " clause to your existing interface. The implementing classes wouldn't need to change at all.

    In the example that I suggested, the only change would be to the asbtract ICollection class. It would change in several ways - INHERITS instead of IMPLEMENTS, INTERFACE instead of CLASS, and remove ASBTRACT from the class definition and all methods. Oh... and the Collection class would have to change to say IMPLEMENTS instead of INHERITS for ICollection.

    Possibly, although the theoretical model is still valid. It's the implementation that's wonky (or however you care to describe it)

    How is the theoretical model valid if each class has to implement the interface itself? In C#, Java and C++ (and boy, I hope this is true in OOABL), you have to explicitly specify which implementation of a method you are implementing if you want to override both interfaces.

    So, for example, lets say I have a IFace1 with method foo and IFace2 with method foo and Class1 implements both IFace1 and IFace2. If I want to override foo for both IFace1 and IFace2, I have to prefix the method foo with the interface in its declaration. Therefore IFace2:foo is different from IFace1:foo in Class1.

    The compilers are optimized to realize that if there is no second declaration, the class is using the same implementation of the method for both interfaces. But that is a compliler optimization.

    So going back to my example, ICollection:IIterable:iterator() is different from IIterable:iterator() in the Collection class.

  • I think the answer here is partitioning.  I.e., since you can't get the interface hierarchy you want, partition it.  Your Iface2, which I presume you would like to have inheriting from Iface1 and that is where it gets foo, should not contain foo.  It should contain only those things which would be defineed in Iface2 itself.  But, I would name it accordingly so that one didn't think it was the same thing as it would be if you had interface inheritance.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • tamhas wrote:

    I think the answer here is partitioning.  I.e., since you can't get the interface hierarchy you want, partition it.  Your Iface2, which I presume you would like to have inheriting from Iface1 and that is where it gets foo, should not contain foo.  It should contain only those things which would be defineed in Iface2 itself.  But, I would name it accordingly so that one didn't think it was the same thing as it would be if you had interface inheritance.

    Actually I think we're losing the point, because I was really making a couple of points. So let me start again:

    IFace1 has method "foo".

    IFace2 has method "bar".

    IFace3 has method "other".

    Both IFace2 and IFace3 inherit IFace1. Thus, both IFace2 and IFace3 inherit method "foo".

    ClassA implements both IFace2 and IFace3.

    If ClassA wants to provide different implementations of "foo" for IFace2 and IFace3, it has to explicitly indicate which one is for which interface.

    Therefore there will be a method declarations for IFace2:foo and another for IFace3:foo.

    Any code that is written against IFace3 will invoked the IFace3:foo method. Any code that is written against IFace2 will invoke the IFace2:foo method.

    This is how true polymorphism works.

    Most compilers assume that if there is no specific declaration of foo for each interface, all interfaces should use the same declaration.

    Now lets take this a step further. Lets say ClassA actually inherits from ClassB which implements IFace2 and ClassA also implements IFace3 (And I have real Java code that does exactly this). ClassB has an implementation of foo (for IFace2). ClassA has an implementation of foo for IFace3. When I execute ClassB:foo, it should execute the foo in ClassB, not the foo in ClassA because the one in ClassA is for IFace3.

    Given this, you and Peter are both asserting that a satisfactory workaround to the fact that we don't have interface inheritance would be to implement the interface in all classes that use it.

    I'm arguing that there are two problems with this:

    1) It violates encapsulation. Classes A through G should not all have to implement IFace1 because IFace2 cannot inherit IFace1. And an abstract class for IFace2 works around this problem.

    2) It creates issues with polymorphism which will be really hard to fix later if you don't know where all those interface implementations are.

  • My problem with your discussion is the basis of the example.  Certainly in a regular generalization, one would implement 2 *or* 3, not both.  The idea of implementing Iface2 and Iface3 when both inherit from Iface1 seems to me to be bad decomposition.  Either they should be three separate interfaces and any given class would implement any combination as needed or, if they have an hierarchical relationship, then the branches become mutually exclusive.

    I think, if one were to follow those design principles, all of these issues go away.  The whole idea of having to decide which foo one is implementing seems like a bad design situation that one should not create.

    Perhaps you can change my mind with a real example, but I don't see this as a problem that needs to be fixed by any change in the language.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • The only change in the language that I am asking for is implementation of interface inheritance which I think is probably on the radar anyway. I started this thread by asking for opinions on workarounds for the fact that there is no interface inheritance.

    You and Peter came up with the suggestion that instead of creating an abstract class to function as the inheriting interface, I should rather implement each interface in each class. The point that I have been trying to make is that that has different polymorphic consequences than the inheritance model that I mentioned.

    As to examples of what I was talking about, I have an interface that is a very generally useful interface called "INamedItem" and it requires a property (C#)/methods (Java) called "Name". I have a number of different things that implement this interface, including an interface called "IDataElement". IDataElement is the base for IParameter and IField in code that works with the OpenClient API. IParameter is extended to Parameter, TempTableParameter and DatasetParameter. IField is extended to DatabaseField and TempTableField. Each of these has a name because they implement INamedItem indirectly.

    I also have an interface called IParameterValue which is extended into a ParameterValue class. IParameterValue also inherits INamedItem. It contains a Parameter object and associates it with a ValueHolder. The ParameterValue class's Name property returns the value of the Name property in the Parameter object.

    In this case, INamedItem is a very simple interface that simply guarantees that I can treat all named items the same way when they get added to a collection. I can always call getName() or use the Name property to index these things in a HashMap. All of the objects I mentioned inherit this interface indirectly through several levels from a parent interface.

    The model you guys are talking about would have me implement INamedItem for every class. The encapsulation argument is obvious. More confusing, though, is that these objects would have to implement two INamedItem interfaces (one directly, and the other through the parent interface) to disambiguate, but there really is no disambiguation necessary.

    I'm thinking about this from the point of view of where we are going to, not where we are right now.

  • A diagram might help ...

    With that qualification I guess I am wondering about a couple of things here.

    One, am I correct that one of your issues is that ParameterValue has two sources of Name?  One from IParameterValue and one from IParameter?

    If so, that bothers me a little since it seems that Name in the context of IParameterValue doesn't mean the same thing as in the context of IParameter.  The former is the name of something contained within while the other is the name of the thing itself.  No?

    Another thing I wonder about is whether there is an opportunity for generalization here.  I.e., isn't there some knowledge or behavior common to the three classes which implement IParameter?

    You haven't said much about what is in each interface, so it is hard to be very concrete about what I would do in this situation, but it seems to me that the one clear problem is having two not quite identical meanings for Name in ParameterValue.  E.g.,, if IParameterValue implemented an interface which contained a property called AssociatedName or something, i.e., language which made it clear that it was the name of the thing contained instead of the name of the thing itself, then the ambiguity would not exist.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • The simplified version of the diagram is at http://www.thesoftwaregorilla.com/wp-content/uploads/2009/08/Class-Model.jpg. It's missing some of the interfaces in between, because the production version of this code is far more detailed than the version on my blog. DataElement implements an interface called IDataElement and a couple of others. Parameter extends DataElement and implements IParameter which deals with adds parameter mode containment. Field extends DataElement and implements IField which has methods to validate that the datatype is supported for a field. These are all metadata-related classes.

    The IValueHolder is will hold any value for any object, and has a generic ValueHolder conrete class. Each of the subclasses specialize the datatype so that it is easier to work with them in code. The DataType enumeration contains methods to deal with instantiating the appropriate value holder.

    A ParameterValue (which derives from IParameterValue which inherits INamedItem) is a container that associates a Parameter's metadata with the value for the parameter in a specific call instance. Actually it's a little more complicated than that. There is a DataElementValue that is the base abstract class. ParameterValue and FieldValue both derive from it. ParameterValue exposes a portion of the contained Parameter object in a read-only fashion. In other words, it does not duplicate functionality - it merely calls the .getName method in Parameter to return its own name.

  • it does not duplicate functionality - it merely calls the .getName  method in Parameter to return its own name.


    This was one of my points.  It is not the same and therefore probably shouldn't have the same name.  It is using the same name for two different functions within the same namespace which seems to me to cause the problem.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com