Clarity in classes implementing multiple interfaces (alternative to delegation):
Asked Answered
W

4

7

Let's say we've got the following:

IFirst = Interface(IUnknown)    
  function GetStuff: Integer;
end;
    
ISecond = Interface(IUnknown)
  function GetOtherStuff: Integer;
end;
    
TFirstSecond = class(TInterfacedObject, IFirst, ISecond)    
private 
  function GetStuff: Integer;        //implementation of IFirst
  function GetOtherStuff: Integer;   //implementation of ISecond;
end;

I have never liked the fact that in TInterfacedObject there seems to be no way to distinguish between which methods implement which interfaces. Am I missing something? Does anyone know a way structure the code to do that? To designate that GetStuff is the implementation of IFirst and GetOtherStuff is the implementation of ISecond? ('Put a comment' is not the answer I'm looking for...)

I know I can use the 'implements' directive to define properties in TFirstSecond for each interface and delegate the implementations to instances contained within TFirstSecond, thereby segregating everything. But I'd like a shortcut...

Woolf answered 26/7, 2011 at 20:48 Comment(1)
@daemon_x - thanks for the nice edit - I was in a big hurry when I posted.Woolf
S
11

In the case you post, I prefer comments (interface name just as NGLN puts it) too, but I would like to explain why the implements keyword can be the best solution in some other cases, just not in trivial cases where there's only one method per interface as in your trivial sample.

I know you said you know about Implements; but for people coming along who have not seen it, I'd like to document when it IS useful, bear with me. It is even worthwhile having all the extra work of having more classes, in some cases.

So I would use implements not as a shortcut (as you see it's longer!) but only when each interface involves 100 methods to be implemented, and where the resulting design has less coupling, and better cohesion and readability only.

So this is an admittedly silly example, but if each of IFirst and ISecond had 100 methods, then it might be a great leap forward...

type
IFirst = interface
  function GetStuff: Integer;
end;

ISecond = interface
  function GetOtherStuff: Integer;
end;

TFirstLogic = class(TInterfacedObject, IFirst)
  function GetStuff: Integer;

end;

TSecondLogic = class(TInterfacedObject, ISecond)
  function GetOtherStuff: Integer;
end;

TFirstSecond = class(TInterfacedObject, IFirst, ISecond)
private
  FFirst:TFirstLogic;
  FSecond:TSecondLogic;
protected


  property First:TFirstLogic read FFirst implements IFirst;
  property Second:TSecondLogic read FSecond implements ISecond;
public
  constructor Create; // don't forget to create and free FFirst/FSecond.
  destructor Destroy; override; // don't forget to create and free FFirst/FSecond.


end;

You could say Implements is the only way we could do "partial classes", or at least create one composite class that implements a bunch of interfaces, and have a bunch of sub-properties (which are protected or perhaps even private) used to do the "implements" delegation. If you move everything else right out of the unit that contains the aggregating class, you could have a really clean design.

See answered 27/7, 2011 at 0:7 Comment(4)
there are 2 or possibly 3 interfaces, each with 15/20 functions. I inherited a big bowl of spaghetti to untangle - and I would have started implementing delegation already - realized this afternoon that I really have to - but I've got time constraints and I already have a lot of new code, so I was looking for something quick that I could use just to enforce some organization and get something 'out the door'. So, I'll give you credit for the answer since you took the time to educate the public and start on delegation tomorrow AM...Woolf
@mikey perhaps you could edit the question to remove the bit that says you don't want to use delegation!Diep
Well he didn't, but he SHOULD use delegation, if he has 2/3 interfaces with 15/20 functions each. (maybe put THAT in the question).See
I think the title got changed. I would have put the title "I don't want to use delegation, but I want to have delegation occur... What should I do?" :-)See
D
16

I suppose the only thing you can really do without using comments is to add method resolution clauses:

IFirst = interface
  function GetStuff: Integer;
end;

ISecond = interface
  function GetOtherStuff: Integer;
end;

TFirstSecond = class(TInterfacedObject, IFirst, ISecond)
private
  function GetStuff: Integer;
  function GetOtherStuff: Integer;
public
  function IFirst.GetStuff = GetStuff;
  function ISecond.GetOtherStuff = GetOtherStuff;
end;

I don't think this really adds very much to the mix, and I personally would consider this worse than without the method resolution clauses.

Diep answered 26/7, 2011 at 21:6 Comment(4)
I forgot about that construct and you've reminded me - and although I don't think it's so bad (what is your problem with it? Redundancy?) I don't think it really solves the problem because my concern is mostly in my implementation section of the code - If I've got 10 complex methods each for 2 interfaces, I'd like the code to self define what interface it's implementing. Method resolution is really not designed for this - it's just a way to avoid name clashing. I suppose I'll have to either live with the messiness or 'man up' and use delegation...Woolf
what you are looking for does not exist I believeDiep
{implementing ISomething} for human benefit really should be a comment, and that's that.See
Method resolution clauses ARE however really really great when you HAVE to disambiguate two method names that would otherwise collide (Of course in that case, I would prefer 100% to disambiguate by delegation only).See
S
11

In the case you post, I prefer comments (interface name just as NGLN puts it) too, but I would like to explain why the implements keyword can be the best solution in some other cases, just not in trivial cases where there's only one method per interface as in your trivial sample.

I know you said you know about Implements; but for people coming along who have not seen it, I'd like to document when it IS useful, bear with me. It is even worthwhile having all the extra work of having more classes, in some cases.

So I would use implements not as a shortcut (as you see it's longer!) but only when each interface involves 100 methods to be implemented, and where the resulting design has less coupling, and better cohesion and readability only.

So this is an admittedly silly example, but if each of IFirst and ISecond had 100 methods, then it might be a great leap forward...

type
IFirst = interface
  function GetStuff: Integer;
end;

ISecond = interface
  function GetOtherStuff: Integer;
end;

TFirstLogic = class(TInterfacedObject, IFirst)
  function GetStuff: Integer;

end;

TSecondLogic = class(TInterfacedObject, ISecond)
  function GetOtherStuff: Integer;
end;

TFirstSecond = class(TInterfacedObject, IFirst, ISecond)
private
  FFirst:TFirstLogic;
  FSecond:TSecondLogic;
protected


  property First:TFirstLogic read FFirst implements IFirst;
  property Second:TSecondLogic read FSecond implements ISecond;
public
  constructor Create; // don't forget to create and free FFirst/FSecond.
  destructor Destroy; override; // don't forget to create and free FFirst/FSecond.


end;

You could say Implements is the only way we could do "partial classes", or at least create one composite class that implements a bunch of interfaces, and have a bunch of sub-properties (which are protected or perhaps even private) used to do the "implements" delegation. If you move everything else right out of the unit that contains the aggregating class, you could have a really clean design.

See answered 27/7, 2011 at 0:7 Comment(4)
there are 2 or possibly 3 interfaces, each with 15/20 functions. I inherited a big bowl of spaghetti to untangle - and I would have started implementing delegation already - realized this afternoon that I really have to - but I've got time constraints and I already have a lot of new code, so I was looking for something quick that I could use just to enforce some organization and get something 'out the door'. So, I'll give you credit for the answer since you took the time to educate the public and start on delegation tomorrow AM...Woolf
@mikey perhaps you could edit the question to remove the bit that says you don't want to use delegation!Diep
Well he didn't, but he SHOULD use delegation, if he has 2/3 interfaces with 15/20 functions each. (maybe put THAT in the question).See
I think the title got changed. I would have put the title "I don't want to use delegation, but I want to have delegation occur... What should I do?" :-)See
J
2

Although you are specifically asking for an answer not involving comments, may I say that using comments is the common solution by more then just the Delphi VCL, as follows to be precise:

TFirstSecond = class(TInterfacedObject, IFirst, ISecond)
private
  { IFirst }
  function GetStuff: Integer;
private
  { ISecond }
  function GetOtherStuff: Integer;
end;
Jugular answered 26/7, 2011 at 22:35 Comment(12)
It's only the human that needs help, thus, it's only the comments that should change. Code is for computers (and readable by you too), but if it's your brain that needs the info, comments are ideal.+See
if it was only the interface section, comments would indeed suffice - as I explained in my comments to David, it's the implementation that I'm more concerned about. And that being the case, seems the consensus is that delegation is the only way.Woolf
Hmmm, I would wonder why the implementation is of any concern at all. In the interface, seeing which parts of the class are present to satisfy an interface (contract) is understandable, but beyond that, how the implementation is organised is entirely irrelevant to any questions about which methods are there for interfaces etc, and organisation for more efficient navigation becomes the key. e.g. I put all my getter/setter methods after constructor/destructors but before any "real" methods, and simply alphabetic order applies... [..cont.]Seringa
[..cont..] if I need to navigate between interface and implementation sections I invariably use ctrl-up/down so using one as an "index" to and concretely reflecting the organisation of the other is unnecessary. If I am on a method "Foo" and want to get to a method "Bar" that I know exists, I know it comes before "Foo" (alphabetically). Meanwhile in the interface I can document that Foo is part of one interface contract and Bar part of another, and the interface that provides Bar may come before Foo. Just an example.Seringa
Agreed - see my comment to warren on his answer.Woolf
Mikey: It's true, it's primarily for the benefit of the computer, but when you need to read it, you need to be able to understand it so you will know exactly what the computer will do with it. When the language isn't perfectly clear (and none ever is) you should comment so you don't (or other people don't) misunderstand or miss something later. Does that make more sense? Obviously you have to write and maintain the code, but it shouldn't contain non-executing comments as if they did something. That would be evil.See
"but it shouldn't contain non-executing comments as if they did something. That would be evil." - Agreed.Woolf
@Warren - "but it shouldn't contain non-executing comments as if they did something. That would be evil." We could open a big can of worms with a discussion about Delphi's properties here (even though they do 'execute')- most languages seem to do perfectly well without them and they are mostly useless if you work with interfaces - you can have access methods without 'properties', which amount to not much more than 'syntactical sugar'...Woolf
Ah but they are not syntactical sugar, they are the basis of the DFM property streaming system, which in turn is the basis of the RAD approach. Interfaces with only methods does not provide an automated persistence mechanism.See
@Warren - A language doesn't need properties to support persistence - it's just the way Delphi's designers chose to implement it. Nor do properties need to be linked to persistence - even in Delphi, they don't have to be. FWIW, I happen to like properties as a syntactical short-cut and elegant way to map access methods. But since I like interfaces even more, and I don't like redundancy, I don't use properties (or RTTI) very often. But we're getting way off topic....Woolf
Delph is what it is. No point calling the design "sugar" when it's not.See
@Warren - of course you're correct. I was just quoting my old boss -LOL.Woolf
H
2

D2006 (but probably earlier versions as well, D2006 is just the earliest I have available at the moment), supports "mapping" of interface methods to specific class functions. This gets rid of the necessity to have a property as you do with the implements keyword.

Using interface mapping should also be a way to remove ambiguity when two interfaces contain the same method signature but need different implementations.

Example:

IMyFirstInterface = interface(IInterface)
  procedure DoSomethingInteresting;
  procedure DoSomethingElse;
end;

IMySecondInterface = interface(IInterface)
  procedure DoSomethingInteresting;
end;

TMyCombinedObject = class(TInterfacedObject, IMyFirstInterface, IMySecondInterface)
private
  // Interface mappings 
  procedure IMyFirstInterface.DoSomethingInteresting = DoSomethingInterestingFirst;
  procedure IMySecondInterface.DoSomethingInteresting = DoSomethingInterestingSecond;
protected
  procedure DoSomethingInterestingFirst;
  procedure DoSomethingInterestingSecond;
  procedure DoSomethingElse;
end;

Drawback if you have lots of interfaces or lots of methods: repetition. However, as you can see in the example, you do not need to specify mappings for all methods in an interface.

For documentation purposes you could put the mapping directly with the actual method declaration, so they stay together and are less likely to get out of sync (or rather that the mappings will miss new methods, as you do not need to declare a mapping for each interface method):

TMyCombinedObject = class(TInterfacedObject, IMyFirstInterface, IMySecondInterface)
protected
  procedure IMyFirstInterface.DoSomethingInteresting = DoSomethingInterestingFirst;
  procedure DoSomethingInterestingFirst;

  procedure IMySecondInterface.DoSomethingInteresting = DoSomethingInterestingSecond;
  procedure DoSomethingInterestingSecond;

  procedure DoSomethingElse;
end;
Harrumph answered 27/7, 2011 at 6:50 Comment(2)
See David's answer above and accompanying comments. TnxWoolf
Sorry for bringing up this old thread, but does anyone have a clue why there is this new syntax compared to property [...] implements? Why wouldn't procedure DoSomethingInterestingFirst implements IMyFirstInterface.DoSomethingInteresting; have been chosen?Baptist

© 2022 - 2024 — McMap. All rights reserved.