There’s a lot of info/discussion on interfaces in the mail archive - also plenty of misconceptions and half truths 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
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.