Interfaces - why are they recommended?

I’m a big user of interfaces but I agree with your statement above. Interfaces shoehorned in after the fact is almost certainly time that could be better spent elsewhere.

Next time you’re designing new code, if you find yourself designing a class hierarchy with new ancestor classes that have a lot of virtual or virtual abstract methods, that’s a good time to try experimenting with interfaces. You would define an interface that has all those virtual and virtual abstract methods you would otherwise have put in your ancestor class.

Even then it’s still going to feel a bit weird, like you could just do all this with classes. The ah-ha moment will come later such as when you decide you want to implement your interface with something that has a completely different ancestor than the other classes.

Interfaces are a paradigm shift and when you throw in the reference counting as well, a big paradigm shift. Don’t worry if the ah-ha moment never comes, classes already give you maybe 75% of what interfaces do (plus classes have other stuff that interfaces don’t).

I think my next task is to learn how use threads so some tasks in my application become non blocking, but I’ll save those annoying questions for another day and thread. :D.

There’s a lot of info/discussion on interfaces in the mail archive - also plenty of misconceptions and half truths :wink: Forget about the comments on COM, or bugs in the IDE with navigation etc. Focus on the benefits to your code, in terms of decoupling and refactoring ease.

You don’t have to implement interfaces for every class in your application!

One thing that I find others don’t do very often is use interfaces with forms and frames.

It’s always bugged me that that all components you drop on a form or frame are published - so visible to anything with a reference to the form or frame. Not a big deal in and of itself (and necessary for serialization of forms) - however over time it leads to (and perhaps encourages) coupling that is hard to unpick when refactoring.

So if I need to reference a form or frame in my applications, I will typically implement an interface that exposes only the api surface that part of the code needs to interact with the from or frame.

Say for example you have a form with a Memo control used for logging, and pass this form around to other parts of the application.

Eventually you will see code like this (a contrived example, in a real app it would probably be way worse).

LoggerForm.LogMemo.Lines.Add('error...');

Of course you could improve that by adding a method

LoggerForm.LogError('error....');

which is of course better - however there is still noting to stop you (or other devs, new hires etc) from accessing the memo directly.

There are two issues there, firstly the calling code really just wants log an error - it really shouldn’t care about how that is implemented - and secondly what happens when the log control is changed to a TSuperColoredAwesomeLogControl - then you are left updating code all over the show.

So how do you fix this - create an interface :wink:

type
  TLogLevel = (Debug, Info,Warning, Error);

  ILogger = interface
  ['{156AC1CA-E300-4F2B-ACF5-A14F8CA32859}']
    procedure Log(const level : TLogLevel; const value : string);
  end;

Implement this interface somewhere (doesn’t have to be a form, could be a class that writes to a text file - easy to change later)

type
  TLogFrame = class(TFrame, ILogger, IClipboardProvider)
    LogMemo: TMemo;
  private
    { Private declarations }
  protected
    procedure Log(const level: TLogLevel; const value: string);
  ....
end;

In this example, the logger is implemented on a frame on the main form, so we get a reference to ILogger

procedure TMainWindow.FormCreate(Sender: TObject);
begin
  FLogger := LogFrame1 as ILogger;
  ....
end;

Then when parts of the code need access to the log, we pass them an ILogger

procedure TMainWindow.Button2Click(Sender: TObject);
begin
  if AnotherForm = nil then
    AnotherForm := TAnotherForm.Create(Application, FLogger);
  AnotherForm.Show;
end;

In the above example, the TAnotherForm doesn’t care how ILogger is implemented, and it doesn’t need to know. It has no way to reference LogMemo since it can only what is exposed via the interface. This decouples the implementation of the logger from the use of it - and frees us to change how logging is implemented without breaking any calling code.

This is actually something I demoed during the Oct 2020 Canberra adug meeting - not sure if that was recorded? @StephenCorbett ?

This is the example I showed

In the future, if we were to implement dependency injection, we would just register the ILogger instance with the container and use constructor injection to inject the ILogger dependency in where ever it is needed.

I always find it easier to understand programming concepts with real world examples - sometimes those can be hard to find. In the package manager that I’m (still) working on, I use interfaces a lot, and I use dependency injection (Spring4D) - worth cloning and exploring the code to look at the above concepts in action.

3 Likes

As part of the major upgrade to my application, I have been converting all the calls to UI objects to call aptly named procedures instead. Being able to prevent direct access to UI objects would be nice (in due course), as that would really force a change in habit.

Vincent’s presentation can be viewed at ADUG Canberra October 2020 - TSVGIconImageList plus Interfaced Forms and Frames - YouTube

2 Likes

Starting at 53:00 ADUG Canberra October 2020 - TSVGIconImageList plus Interfaced Forms and Frames - YouTube

1 Like

I have watched that (section of) video now and think I’ll leave Interfaces for sometime further down the track.

Just a note that Marco has two ‘courses’ related to interfaces …

Yeah, you can. The as keyword was extended awhile back to let you cast back to TObjects.

http://docwiki.embarcadero.com/RADStudio/Sydney/en/Interface_References_(Delphi)#Casting_Interface_References_to_Objects

Not saying you should, mind you, but you can.

Cheers
Malcolm

4 posts were split to a new topic: Code Navigation

A post was merged into an existing topic: Code Navigation

Hi David,

several answers here and not a single word against interface usage. Looks like it’s too good to be true, right? My grandma used to say that if something looks too good to be true, it definitely is.

There are several CONS regarding interface usage:

  • Interfaces are a contract, and like real contracts they are hard to break. Whenever you need to add a new method or property to your interface, all implementations need to be changed. If you need to remove a deprecated method from an interface… well… forget it… When this involves 3rd parties (imagine that you are writing a library) it becomes a nightmare. The “solution” is to create a new interface and suddenly your object now implements 20+ interfaces (have a look at WinAPI.ADOInt.pas and you will know what I mean).

  • When your object implements a new interface, its instance size grows by the size of a pointer. So, if your object implements 4 interfaces, in x86, the InstanceSize will be 24 bytes instead of 8 bytes. If you follow the design of .NET classes for instance, where every core class like lists, dictionaries, etc., implement several interfaces (sometimes more than 10), the memory usage of your application will grow exponentially. This may be irrelevant for desktop applications, but it’s nasty for any server application.

  • Delphi IDE has poor support for Interfaces (opposed to Visual Studio, for instance). You just can’t determine what classes implement a specific interface. Code navigation becomes a nightmare inside Delphi IDE as well. Ctrl+Click just won’t take you to the object implementing the interface, but to the interface declaration (probably in another unit), so it becomes mostly useless. Be prepared to make Grep search your best friend…

  • Virtual method calls in interfaces are really slow. For instance, if you decide to write a TList which implements IList interface, a virtual Get() method will suddenly become many times slower than the direct method call. If the method is already slow, fine, the interface overhead won’t be significant. But in my previous example, it will definitely be.

  • Mixing object references and interfaces is in general a very bad idea which ends up with nasty bugs, memory leaks and all sort of problems.

  • Finally, also opposed to .NET implementation, Delphi interfaces carry a COM baggage, so you need to either descend your object from TInterfacedObject (there are a few other options in recent Delphi versions) or implement QueryInterface, _AddRef and _Release methods in every single class you write. If you decide to inherit from TInterfacedObject then you need to deal with the mixed memory management mode: some objects need to be freed explicitly, others don’t, which causes more problems than solves, IMO.

Sometimes all you need is an abstract class instead of an interface. For instance, if your class does not descends from a VCL/RTL specialized class and you only implement one single interface, probably you will be better with an abstract class.

My recommendation: think about all pros and cons (yes, they exist) well before going down that path.

1 Like

All of your points above are quite valid (if perhaps a bit over stated), but I’m going to disagree on the COM baggage - yes - interfaces were added to enable com support, but they are not tied to com - you don’t need a type library or any registration etc for objects that implement interfaces. I see this FUD trotted out every time interfaces are discussed. If you are using interfaces that does not mean you are using COM.

Yes, _AddRef and _Release are part of IUnknown - which is a com interface, but they enable reference counting memory management which is one of the many benefits of using interfaces.

Like any other language feature in any programming language - you need to learn how to use them and understand the benefits and issues with the feature to make an informed decision.

COM baggage is exactly the terminology used by Marco Cantú when I questioned him why they just don’t remove this (let’s call it) annoyance from Delphi interfaces.
BTW, I never mentioned type libraries or I never said that interfaces means that you are using COM. But you do need to implement 3 useless methods because of the way interfaces were born strictly connected to COM (translation: they carry a COM baggage, IMO).

Cheers

Whilst I would love to see an interface type that doesn’t implement reference counting (so the compiler doesn’t insert calls to addref and release) - I don’t see that as baggage - it’s a useful feature.

Some devs have an irrational fear of com - so when people say interfaces is com baggage - they are dissuaded from using them.

1 Like

To me this is not so much a “con” of interfaces but of their design. Plus, there is a way to make a default implementation method but I am not going to talk about it because it’s unclear to me whether it is intended (and can therefore be relied upon long-term) or it’s not intended, in which case at any point in time your code could break and you’d be in trouble.

Are we really talking about bytes here? Seriously? Also, again, design matters. If your interfaces are designed the way they should, this should not be a problem.

I don’t disagree with this, but I have rarely ever had a need to do this.

I am not sure what you’re trying to say here. Would you mind to be more specific?
One thing I always do for my interfaces is to either have an enumerator or a ForEach method with anonymous procedure and function calls. This makes the need for virtual getters and setters basically go away.

I quite like the fact that you can’t mix things, in fact. I know it’s not great from a coding perspective, but from the design perspective it forces you to really think about what you’re doing. Besides, there are ways to do this (indirectly) if you really need to, but you should never do it even if it were allowed.

I disagree. While it’s true that interfaces were introduced with COM and while it’s true that they retain those support methods, today’s interfaces aren’t tied to com.
IUnknown is an alias for IInterface nowadays and has been so for quite some time.

A

EWWWWWW!!!

That’s gross, man. Who thought that was a good idea?

Yes, I am. Why? Maybe you never developed an application that needs to handle millions of object instances at once, where every byte counts. But the fact that you doesn’t know it doesn’t make it inexistent, right?

I’ll let Andreas explain it to you:
https://www.idefixpack.de/blog/2016/05/whats-wrong-with-virtual-methods-called-through-an-interface/

“Carries COM baggage” <> “tied to COM”

As I mentioned to Vincent above, I borrowed this terminology from Marco Cantú. I think he knows what he’s talking about.

Cheers

I can’t remember using it as an integral design aspect of my code but I know I’ve relied on it to peek behind the curtain so to speak in unit tests, logging code and temporary debugging solutions. In that sort of limited, non-crucial use scenario I think it has a place.

2 Likes

The issue Alexandre mentioned was already reported my some while ago:
https://quality.embarcadero.com/browse/RSP-18108

I wrote about the improvements that I made in Spring4D which eliminated all virtual methods behind the collections in my blog post some while ago. So the effect is real but the funny thing is: every time you have been using a try finally somewhere you suffered from the very same effect until Delphi 10.4.2! See this issue: Log in - Embarcadero Technologies - did anyone ever think of not using try/finally because of that?

The case of using up instance size for the interface table pointers can be a real issue indeed and this was also something I worked on - implementing interfaces across multiple inheritance levels was a bad idea so I only implement them all on one class so the interfaces that inherit from each other are put into the same slot - all spring collection types should only ever consume SizeOf(Pointer)*2 space for that. To compare: RTL TList takes 44 bytes, an IList instance 56 bytes on 32bit, the numbers for 64bit are: RTL 88 bytes and Spring 104 bytes.

If you have millions of object instances you probably don’t want them to implement objects which would be wrong anyway I would guess as they are probably just data-holding objects and thus don’t need to be handled via interfaces (see Nick Hodges DI book on creatables vs injectables). If these objects however have lists the thing might be different. But then it depends on how many items there are actually in those lists - if it’s just a handful then the overhead for those list instances is major and you might rather use a dynamic array (which also has some small overhead you know).

1 Like