Recent

Author Topic: interfaces  (Read 662 times)

fcu

  • Jr. Member
  • **
  • Posts: 64
interfaces
« on: April 17, 2020, 08:18:06 am »
Hi
I've read fpc wiki about how to uses interfaces , but i really didn't get the benefit or when using it ?!
i mean what could be achieved with interfaces and cannot be with only classes ? 
thanks

af0815

  • Hero Member
  • *****
  • Posts: 553
Re: interfaces
« Reply #1 on: April 17, 2020, 08:44:11 am »
A interface is like a contract. You can Objects with interfaces more decoupled. If a class implement the interface you can work with this object, only the interface have to be documented. A good sample is eg. Directshow from MS. You know only the interfaces and can work with the components/api
regards
Andreas

devEric69

  • Sr. Member
  • ****
  • Posts: 305
Re: interfaces
« Reply #2 on: April 17, 2020, 09:48:11 am »
Yes, basically, an interface is indeed like a contract which must be respected in order to implement a full specific functionality in a Class.

Then, a Class can implement several interfaces-contracts-functionalities: it emulates multiple inheritances in Object Pascal.

Quote
but i really didn't get the benefit or when using it ?!

About the when:
• For what I've seen most often: interfaces are a process often used when extracting features from existing Classes (bottom towards up): when we have a hierarchy of Classes, with some Classes  that now carry several (too many?) roles, features that overlap with other Classes's role(s), then a refactoring must be done: to do this, we extract the methods that form together a feature; then each role-feature becomes an interface (or an abstract class).
In short, interfaces (or abstract classes) are used to enumerate the possible existing roles-functionalities; and thus we can serialize\do a refactoring more clearly, by deciding which Class should implement alone a role or several related roles, through the implementation of one or several interfaces.

• After that, very experienced developers do up to bottom: they start by listing the necessary roles-features first, and create their corresponding interfaces.



For information, there's a thread (here: https://forum.lazarus.freepascal.org/index.php/topic,46181.msg328457.html#msg328457) on how (problems of 'references counting', versus "old-fashioned" OOP with 'Free', versus a mixture of the two, interface delegation, corba, etc) to use the interfaces (if necessary, or to understand the underlying memory management mechanisms involved, when reading the way they have been implemented).

« Last Edit: April 17, 2020, 10:42:34 am by devEric69 »
use: Linux 64 bits (Ubuntu 18.04 LTS).
Lazarus version: 2.0.4 (svn revision: 62502M) compiled with fpc 3.0.4 - fpDebug \ Dwarf3.

Warfley

  • Full Member
  • ***
  • Posts: 193
Re: interfaces
« Reply #3 on: April 17, 2020, 12:18:21 pm »
I think the best usage is explained by the "Delegator Pattern".

Let's say you are writing an Image class, and you want to have a draw Method. You can either draw using a TCanvas onto a TWinControl or TGraphicsControl, or via ASCII art onto a console (e.g. for old text adventures). Rather than having something like this:
Code: Pascal  [Select][+][-]
  1. if target is TWinControl then
  2.   Image.DrawOnCanvas((target as TWinControl).Canvas)
  3. else if target is TGraphicsControl then
  4.   Image.DrawOnCanvas((target as TGraphicsControl).Canvas)
  5. else if target is TConsoleApplication then
  6.   Image.DrawToConsole;
You could use an interface with drawing primitives and inherit from that:
Code: Pascal  [Select][+][-]
  1. IImageDrawer = interface
  2.   DrawRect(x, y, w, h: Integer);
  3.   DrawCircle(x, y, r: Integer);
  4.   DrawLine(x1, y1, x2, y2: Integer);
  5. end;
  6.  
  7. TCanvasDrawer = class(TObject, IImageDrawer)
  8. private
  9.   FCanvas: TCanvas;
  10. public
  11.   DrawRect(x, y, w, h: Integer);
  12.   DrawCircle(x, y, r: Integer);
  13.   DrawLine(x1, y1, x2, y2: Integer);
  14. end;
  15.  
  16. // And
  17. TMyConsoleApplication = class(TConsoleApplication, IImageDrawer)
  18. ...
  19.   DrawRect(x, y, w, h: Integer);
  20.   DrawCircle(x, y, r: Integer);
  21.   DrawLine(x1, y1, x2, y2: Integer);
  22. end;
  23.  
  24. // And usage:
  25. Image.drawTo(target as IImageDrawer);

Note that you don't need to change existing code, but only add the interface. your TMyConsoleApplication still has the same OOP hierachy as before, you just added the additional information needed for the image to draw.

Interfaces "promise" that they implement functionality, without any information how they do so. You can have completely different implementations, as long as their "black box" behaviour is what they promise to do.
Inheritance on the other hand tells you not only about the "black box" behavior, but also about the functionality. If I inherit from TStream, I know that much of the functionality will be provided as by TStream.

fcu

  • Jr. Member
  • **
  • Posts: 64
Re: interfaces
« Reply #4 on: April 17, 2020, 01:37:55 pm »
thanks guys , this is very informative

mr-highball

  • Full Member
  • ***
  • Posts: 176
    • Highball Github
Re: interfaces
« Reply #5 on: April 17, 2020, 06:12:06 pm »
Also useful for dependency injection. A simple example might be for a class that depends on communicating to a database or web server to function. This makes unit testing impossible without relying on the open connection. Interfaces let you inject the concrete implementation or a "mock" implementation, and the receiving class won't know the difference (allowing testing without an open connection).
Also a nice thing is that because they are reference counted, they can be passed around, returned from functions, set as properties and will be automatically freed once all references are decremented. This makes questions like "do I need to free this...?"unnecessary, because there is a predefined pattern for memory management.
(Sorry for the brevity, not on a computer to type out a code example)

af0815

  • Hero Member
  • *****
  • Posts: 553
Re: interfaces
« Reply #6 on: April 17, 2020, 08:33:33 pm »
reference counting for interfaces can be good, but sometimes a hell. And this to debug and find is more than hell. And not all interfaces are reference counted, corba isn't. Few time ago there are lot of discussion about the type of interfaces here in forum.
 
regards
Andreas

mr-highball

  • Full Member
  • ***
  • Posts: 176
    • Highball Github
Re: interfaces
« Reply #7 on: April 17, 2020, 08:47:51 pm »
Yeah corba isn't forgot to mention that. I've found it to be more flexible than just classes alone.
My general checklist is:
* local methods will free once out of scope
* if assigned to a private var nil the reference in your destructor
* passed in as an argument to a procedure, set a local reference var

I think the benefits are there and clear things up when passing around objects (in which who knows who owns the responsibility to free...).

With this being said, there's plenty of times where a simple class will be simpler, or just a record. Highly depends on what you're trying to make.

fcu

  • Jr. Member
  • **
  • Posts: 64
Re: interfaces
« Reply #8 on: April 17, 2020, 11:47:06 pm »
thanks
but seems (inline) doesn't work for methods inside a subclass that inherit from interface !!
i just checked the generated asm file and (call) is still using

fcu

  • Jr. Member
  • **
  • Posts: 64
Re: interfaces
« Reply #9 on: April 17, 2020, 11:53:38 pm »
simpe test
Code: Pascal  [Select][+][-]
  1. type
  2. iobject = interface  
  3.  ['{912AC5B9-4595-46D5-B871-AFB840763116}']
  4.  procedure change(i:longint);
  5. end;
  6.  
  7.  tobject = class(tinterfacedobject,iobject)
  8.   public
  9.    procedure change(i:longint);inline; // never inlined
  10.    procedure show();
  11.   private
  12.    data: longint;
  13.  end;
  14.  
  15. procedure tobject.change(i:longint);
  16. begin
  17.  data:= i;
  18. end;
  19.  
  20. procedure tobject.show();
  21. begin
  22.  writeln(data);
  23. end;
  24.  
  25. var
  26.  iobj: iobject;
  27.  tobj: tobject;
  28. begin
  29.  tobj:= tobject.create;
  30.  iobj:= tobj as iobject;
  31.  iobj.change(16);
  32.  tobj.show();
  33.  
  34.  readln;
  35. end.
  36.  
  37.  
« Last Edit: April 17, 2020, 11:55:44 pm by fcu »

Warfley

  • Full Member
  • ***
  • Posts: 193
Re: interfaces
« Reply #10 on: April 18, 2020, 12:38:40 am »
This is one of the properties of Interfaces. Take this example:
Code: Pascal  [Select][+][-]
  1. ITestInterface = interface
  2. procedure Foo;
  3. end;
  4. // TTest1 and TTest2 implement ITestInterface:
  5. procedure TTest1.Foo;
  6. begin
  7.   WriteLn('Foo');
  8. end;
  9.  
  10. procedure TTest2.Foo;
  11. begin
  12.   WriteLn('Bar');
  13. end;
  14.  
  15. ...
  16.  
  17. var
  18.   test: ITestInterface;
  19. begin
  20.   if Random mod 2 = 0 then
  21.     test := TTest1.Create as ITestInterface
  22.   else
  23.     test := TTest2.Create as ITestInterface;
  24.  
  25.   test.Foo;
  26. end;
In this case the compiler can't know which of the two to inline, as it is literally random. So the first observation is that in the general case, you can't inline calls to interface functions, because the called function is runtime dependend.

The second observation we as humans are really good at is to see that this actually can be inlined:
Code: Pascal  [Select][+][-]
  1. begin
  2.   if Random mod 2 = 0 then
  3.   begin
  4.     WriteLn('Foo') // Inline TTest1.Foo
  5.   else
  6.     WriteLn('Bar'); // Inline TTest2.Foo;
  7. end;

This is can be achived with data- and control-flow analysis. Oversimplified is the calling of an interface method nothing more than calling a function pointer inside a record. So from a dfg and cfg point of view, whats written there is:
Code: Pascal  [Select][+][-]
  1. var
  2.   fp := procedure of object;
  3. begin
  4.   if Random mod 2 = 0 then
  5.     fp := @TTest1.Create.Foo
  6.   else
  7.     fp := @TTest2.Create.Foo;
  8.   fp();
  9. end;
So there are two branches through this code, either the if is taken or the else is taken. An analysis of both paths shows that in the end always the function written in fp will be called, but the value of fp is soly dependend on the branch taken. So the call can be moved into the if-statement and then it can be inlined.
This is called devirtualization, it is when (through static analysis) the compiler can prove that at a given point in the cfg only a certain virtual type is possible, and can therefore elimite the function pointer call with a normal function call.

This is something we humans are naturally good at detecting. But on a formal level this is hard (in the sense of computationally hard). The number of paths grows exponentially with the number of branches (if-statements). Meaning, if you have 3 conditions upon which combination the result of the function pointer depends, you have 2^3 = 8 different paths to check.
In fact many programs, including nearly all GUI applications, have an infinite number of execution paths (e.g. I could press button 1 once, twice, three times, etc, each resulting in a new execution path). Therefore this form of optimization is not only hard, but in general even impossible.
Also the assignment and the call can be seperated by thousands of levels of indirections (jumps to other methods, setting of flags, etc.) meaning even if this would be computationally possible, you would run out of memory pretty fast.

So to summarize, inlining of interface methods is in general not possible (i.e. undecidable). But there are certain cases where devirtualization is possible. The most obvious one is your example. Only one execution path, and assignment and call are not more than 1 instruction apart.

I don't know much about the inner workings of the FPC, when I have to write performance heavy code I use C++. And for both the GCC and LLVM I'm quite sure that on optimization level 2 or greater, this would be devirtualized by them.
I don't know much about the FPC optimizer, but if it isn't capable of doing something comparable for it's native targets, there is an LLVM target (in development), so maybe take a look into that.

But always remember, as Donald E. Knuth wrote:
Quote
Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.
So if it's not performance critical code and interfaces improve the readability and maintainability of your code in most cases they are worth it.
And if you run into performance issues and your profiler tells you that you are wasting a lot of Time inside your Interfaces, consider a rewrite, but if you are not at that point now, this is the last thing you should bother about

zamronypj

  • Jr. Member
  • **
  • Posts: 90
    • Fano Framework, Free Pascal web application framework
Re: interfaces
« Reply #11 on: April 18, 2020, 01:44:12 am »
Hi
I've read fpc wiki about how to uses interfaces , but i really didn't get the benefit or when using it ?!
i mean what could be achieved with interfaces and cannot be with only classes ? 
thanks

While interface may looks similar to abstract class, it has difference. When you pass an interface to a class, that class can have access only to methods defined in that interface or its parent interface.

Code: [Select]
IRead = interface
     procedure read();
end;

IWrite = interface
     procedure write();
end;

TReadWriteAbstract = class(TInterfacedObject, IRead, IWrite)
public
     procedure read(); virtual; abstract;
     procedure write(); virtual; abstract;
     procedure doSomethingElse();
end;

....

procedure TAClass.doRead(reader : IRead);
begin
    reader.read();
end;
...
procedure TBClass.doWrite(writer : IWrite);
begin
    writer.write();
end;

procedure TCClass.doWrite(writer : TReadWriteAbstract);
begin
    writer.write();       
end;

...
var a:TAClass;
      b:TBClass;
      c : TCClass;
      rw :TReadWriteAbstract;

a.doRead(rw);
b.doWrite(rw);
c.doWrite(rw);

When you pass IRead instance to TAClass.doRead() method, you make a contract that you will only need read only access. you can only call IRead.read() method, nothing else. We apply least privilege principle here.  Compiler will prevent you from accidentally calling other TReadWriteAbstract  methods such as write() or doSomethingElse() because TAClass has no notion of TReadWriteAbstract. So interface can promotes loose-coupling when you design interface properly.


When you pass abstract class as in TCClass, compiler won't prevent you from accidentally call doSomethingElse() even if TCClass.doWrite() actually only need write access. It becomes developer job to make sure not to call irrelevant method available in abstract class.

If later, you do need to doSomethingElse() inside TCClass.doWrite(), you simply add method call. But when interface is used like TBClass.doWrite(), developers are forced to rethink about overall application architecture.
« Last Edit: April 18, 2020, 01:56:43 am by zamronypj »
Fano Framework, Free Pascal web application framework https://fanoframework.github.io
Personal Projects https://v3.juhara.com
Github https://github.com/zamronypj

PascalDragon

  • Hero Member
  • *****
  • Posts: 1744
  • Compiler Developer
Re: interfaces
« Reply #12 on: April 18, 2020, 11:23:15 am »
thanks
but seems (inline) doesn't work for methods inside a subclass that inherit from interface !!
i just checked the generated asm file and (call) is still using

This is by design. An interface's method in general can't be inlined, because the compiler does not know what is behind the interface (it could be some FPC class, it could be something in another library, it could be a manually constructed VMT, it could be some COM object). And in the few cases that the compiler has the necessary knowledge (e.g. you assign a class to an interface and use the interface in the same method) then why use an interface at all? The effort to implement this is not really worth it.

I don't know much about the inner workings of the FPC, when I have to write performance heavy code I use C++. And for both the GCC and LLVM I'm quite sure that on optimization level 2 or greater, this would be devirtualized by them.
I don't know much about the FPC optimizer, but if it isn't capable of doing something comparable for it's native targets, there is an LLVM target (in development), so maybe take a look into that.

FPC does have a while program optimization (it's a two-pass approach and thus needs to compilation invokations) that supports devirtualization. However this only works for classes. For interfaces there'd be the added problem that the reference count handling (at least for COM interfaces) still needs to be done correctly.

Also the LLVM backend won't change anything here as the methods will be called using a VMT. It's made sure that LLVM won't touch those.

 

TinyPortal © 2005-2018