0

I encounter this issue multiple times, but I'm never sure how best to deal with it.

Basically, some methods I write require the implementing object to support multiple interfaces. The example I have in mind is that a table structure needs to be able to IIterate over its records and IStreamable its content, where

IIterate = interface
  procedure First;
  procedure Next;
  property EOF: boolean;
end;

IStreamable = interface // outputs current record in table only
  function StreamOut: TArray<byte>;
  procedure StreamIn(const Input: TArray<byte>);
end;

Now I don't think I want to combine these interfaces, as it's rare for methods to require IStreamable and not every IStreamable class can IIterate (my use case has single-record structures too).

The method I am trying to write is

CopyTable(InIterate: IIterate; InStreamable: IStreamable; OutIterate:IIterate; OutStreamable:IStreamable); 

Where the In and Out interfaces must be implemented by the same object. At the moment I'm just asking for one of the interfaces and using a Supports call to get the second, though this doesn't give you any compile-time protection. The obvious alternative is to use the above structure and check that both interfaces have the same implementing class, but that looks ugly.

Is there a better way to work with this kind of problem and get some compile-time safety?

The implementing language is Delphi (XE2), though I think this is a fairly language agnostic question for languages that don't have multiple inheritance.

  • If CopyTable requires a parameter to implement two interfaces, then there's a very high change that it's breaking the single responsibility principle and is doing too much. Refactoring that function into two or more smaller, more focused functions should make the problem go away. – David Arno Dec 3 '15 at 14:33
  • @DavidArno At some point something will need to use both interfaces in conjunction - if there's two simpler classes consuming one each then there will need to be an aggregation of those two classes at some point as the underlying process being performed needs to use both interfaces to complete it's task - it's just abstracting that problem further down the chain – Matt Allwood Dec 8 '15 at 11:58
  • Are you free to (drastically) modify the IIterate interface? – Mike Nakis Dec 8 '15 at 13:05
  • @MikeNakis Yes, I own it and it's not something exported by the third-party DLL – Matt Allwood Dec 8 '15 at 13:46
2

First of all, Iterate is a lame name for that interface, it should be called Iterator, so I will be calling it Iterator in this answer, because I am allergic to bad naming.

You say that you encounter this issue often, and I have to believe you, but I also ought to mention my experience, which is that I have rarely, if ever, encountered this need in the last couple of decades that I have been doing OOP.

Usually, when such a need appears to arise, it is an indication that some other aspect of the design needs refactoring.

The first suspect to examine is the CopyTable() function: why does it need both an Iterator and a Streamable for both the source and the destination table? Why can't it just stream-out the source table to bytes and then stream-in the bytes into the destination table? And why is it even called CopyTable when it is probably just CopyStreamable() or CopyStreamableIterator() at worse?

If there is nothing that can be done about the function, then the best approach in my opinion is to combine the two interfaces into one. The name CopyStreamableIterator() which describes what the function does immediately tells you that you need a StreamableIterator interface which combines Streamable and Iterator. There is nothing wrong with that, and you do not even have to make your Table class implement that new interface, you can simply implement a StreamableIteratorDecorator (see decorator pattern) which accepts a Streamable and an Iterator as constructor parameters and implements the StreamableIterator interface by delegating to them. (Though you will probably just declare your Table class as implementing this interface to be done real quick, because your Table already implements these methods, so it is good to go.)

Note: I am presuming that in Delphi, just as in Java and C#, a class may extend only one class but multiple interfaces, and that an interface may also extend multiple interfaces, but even if by any chance this is not true, this functionality can be emulated by aggregating interface implementations into classes and adding to classes and interfaces methods that return sub-interfaces.

Amendment

Thinking about it a bit more, I am realizing that your problem may lie in that ill-named IIterate interface. This interface is trying to cut corners, so it is built in such a way as to force manipulation of the internal state of the underlying (implementing) object. This causes problems, because it is forcing you to have two separate interfaces, one for altering the internal state of your table to select a row, and another interface for reading/writing the currently selected row.

In a language like C# and Java your table would implement IIterable<IStreamable>, and IIterable<T> would have just one method, newIterator() : IIterator<T>. In turn, IIterator<T> would be defined as follows:

IIterator<T> = interface //javaesque
  function Next : T;
  property EOF: boolean;
end;

or as follows:

IIterator<T> = interface //csharpesque
  function Current : T;
  function HasNext : boolean;
  procedure MoveNext;
end;

So, instead of selecting rows, your iteration would be yielding each row which can then be streamed. So, you only need to pass an iterator (or better yet an iterable) to your CopyTable() function.

Adding rows to the target table can be easily accomplished by implementing a IConsumer<IStreamable> interface which simply appends the contents of the IStreamable as a new row at the end of the table.

  • This is a bit of a case where some people choose an API and others have an API thrust upon them. The API I need to use has a record structure and then another type of structure that basically represents an array of structures. The way that it is written needs a loop of FillStruct/Next calls rather than just blatting the whole structure at once, which seeing that I'm trying to re-use the FillStruct code from the single record case kind of precludes this. – Matt Allwood Dec 8 '15 at 12:07
  • Making a StreamableIterator interface is one way of doing it, but it in practice it will just add an extra layer to my code that isn't serving much purpose. If I was passing this interface around a lot then it may be worth considering, but for the times it's used CopyStreamableIterator(GetStreamableIterator(SrcStreamable, SrcIterator), GetStreamableIterator(DestStreamable, DestIterator)) doesn't add enough clarity to warrant the code bloat in this case IMHO. – Matt Allwood Dec 8 '15 at 12:11
  • Sure, code bloat is an issue. (But if I recall correctly, Delphi is derived from Pascal, which allows classes and methods to be declared local within a function, so you may be able to contain most of the new code right there where it is being used. Maybe.) – Mike Nakis Dec 8 '15 at 13:07
  • You're correct, but I'd still have to have a function that takes two interfaces just to return an interface that could then be passed into a new function. As I'm only really using this structure in one or two places (and it's unlikely to balloon) then I may as well just let the final function take the two interfaces and deal with it rather than spending too much time tying myself in knots trying to make it pretty – Matt Allwood Dec 8 '15 at 13:45
  • 1
    I like what you did with the <sup>'s. When I first saw this answer, my initial reaction to the first paragraph was "what a terrible way to start an answer." But adding the <sup>s made those passages look like [magazine] sidebars without relegating them to footnotes. Kudos. – Robert Harvey Dec 12 '15 at 20:38
1

Java solved it by simply allowing multiple inheritance for interfaces. What is more interesting it also has some support for intersection types (but only for generics). So it is possible to simply write:

<T extends InIterate & IStreamable > void something(T argument) {
    InIterate i = argument;
    IStreamable s = argument;
}

It is type safe and no cast are required. In Java 8 it is also possible to use such syntax as type for lambda expressions but there still isn't any way to simply declare variable as InIterate & IStreamable

  • That seems the most obvious solution, unfortunately it's not a solution available to me in my implementing language :-/ – Matt Allwood Dec 8 '15 at 12:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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