What is the advantage of using an interface if I always get the same behavior using simple classes in Delphi?

Asked

Viewed 1,382 times

5

First of all, I want to point out that this issue DOES NOT ADDRESS comparison between abstract classes and interfaces, it aims to find out if there is any advantage in using interfaces to the detriment of the use of ordinary classes and basic OO concepts. Double question information does not apply

Follows the text of the question:

The interfaces have always been a somewhat obscure subject for me. I just can’t find use for them in my day-to-day life, even though so many people say they’re a powerful resource. Everything I do with interfaces I can do with simple class inheritance.

Along with the answer, it would be interesting for someone to propose some PRACTICAL problem that can only be solved with the use of interfaces. Please do not let there be any problem related to COM/COM+ technology that requires interfaces "simply because yes". The best example would be something simple, using real classes and an explanation of why the problem is only solved using the interfaces, or at least, if it is solved better using them.

I want to remind you that the answer should take into account that you are using Delphi to implement, because Delphi may have classes with virtual and abstract methods that are not interfaces, but behave as such.

  • 1

    @Danielomine doesn’t think it’s duplicate. This question here is much more specific. The other is completely generic (and too broad), since those who asked did not specify any parameters for their doubts. The other doesn’t know the difference between class and interface. Here one wonders the advantage of interfaces if it is possible to accomplish "everything" with class inheritance.

  • @Danielomine, if that’s possible, please clear my question as duplicate. I already edited it to clarify that it is not a comparison between Interfaces and abstract classes. I think removing that duplicate statement will help other people who, like me, have the same question. Following the link indicated in the duplicate text will only disturb the understanding. Thank you!

  • 1

    As @Loudenvier said, this question is not duplicate anything, firstly because the context is not generic and secondly, I want to know the advantages (if any) of using interfaces, because I already know that an abstract class mixes the concepts of interface and ordinary classes. Thank you!

  • Sorry Carlos, but the question is conceptual. So it is duplicate of the suggested link.

  • All right, I’ll just ignore it then, since from my point of vita (and others) it has nothing to do with the other except the fact of citing classes and interfaces

  • 1

    @Danielomine just because it is conceptual does not make this question a duplicate of the other. I think they were too rigid in this marking. However, I also find it interesting that those who see this question can access the other, much broader and generic, to understand the concepts of classes and interface itself, and not the advantages of both in specific contexts. I also suggest to the "questioner" to get used to having their questions and answers under the scrutiny of others and not take it personally! Stack Overflow is revolutionary and works great!!!

  • Take a tour to better understand http://answall.com/tour

  • The questions may be different but the answers are "equal".

  • The answer given here could have been given there. The answers there answer what was asked here. So it’s duplicate according to the definition of duplicate that we use. The question doesn’t need to be exactly equal to be considered duplicate.

  • @Loudenvier, just to be clear, I didn’t take it personally, I just won’t accept something that doesn’t make any sense. In my holy stupidity, I see no relation between the supposed duplicate and my question other than the fact that both involve classes and interfaces. I didn’t want to know the difference between a class with abstract methods and an interface. I learned this in "pre". I really wanted to understand what the usefulness of an interface was when I lived until today without ever actually having used it and you gave the explanation I needed. Mission Accomplished

Show 5 more comments

1 answer

14


Not everything you do with interfaces can be done with classes. An interface is a contract, it does not imply any implementation, whereas a class implies an implementation, even if it has the possibility of using abstract and virtual methods. An interface, therefore, can be implemented by any class, in any class hierarchy. Already using only classes would imply that absolutely ALL objects involved in some operation would necessarily have to belong to the same class hierarchy.

Generally speaking:

  • Inheritance implies a relationship "was" (Official is a Person)
  • Interface implies a relationship "can" (Bitmap can be displaced)

I will respond without implementation code (and perhaps not 100% syntax) to avoid limiting this answer to Delphi, since your question is for general use for Object Orientation.

Imagine you want to implement a standardized form for serialization of classes for JSON (or any other format). If you choose the path of inheritance you must, obligatorily, force that all objects inherit from the same parent. This may be undesirable as it will introduce a class coupling with no relation to each other. Your object hierarchy will no longer be pure, we can say.

With classes you’d have to have something like this:

TSuperClassePaiDeTodos : class
published
    function Serializa : String; virtual; abstract;
end;

TUsuario : class(TSuperClassePaiDeTodos) 
published
    function Serializa : String; override;
end;

TLancamentoFiscal : class(TSuperClassePaiDeTodos)
published
    function Serializa : String; override;
end;

TOperadorMatematico : class(TSuperClassePaiDeTodos)
published
    function Serializa : String; override;
end;

There is no relationship between a Tax Release and a User (much less a mathematical operator you use, for example, to interpret user-created dynamic expressions), but using class inheritance to force "a contract" (or an interface)in this case forced us to create a relationship between these classes: all inherit from the same parent class and require the implementation of the Serializa method.

That wouldn’t be so bad, since we want them all to be serializable. But what if we need some behavior that is common among some classes, but not in others (imagine you have hundreds of classes, divided into several hierarchies, all inherited from the parent class). What would you do in this case? You would go there in the "father of all" class and add an abstract method. The ripple effect of this is huge: all its hundreds of classes would have to implement this new method, even those who don’t need it.

Imagine that we want to add, for example, audit functionality, which is common to several objects, but not all. For example, it makes sense for a User and an Accounting Release to be audited, but never a Math Operator. How to Elegantly Solve This?

You could simply make an empty implementation of the audit method, but it already starts to sound like a gambit. And it can still have unexpected effects if the code that calls the audit method is expecting some specific behavior from those who implement this contract/interface/function, such as updating some counter, allocating some area of memory, etc. You end up having an object that implements the audit method in theory, but not in fact. It sounds bad, no?

You could try modifying the class hierarchy to figure out a way to put a common parent only among classes that need auditing. This is still easy with only three classes:

TSuperClassePaiDeTodos : class
published
    function Serializa : String; virtual; abstract;
end;

TSofreAuditoria : clas(TSuperClassePaiDeTodos)
published
    procedure Audita: virtual; abstract;
end;

TUsuario : class(TSofreAuditoria)
published
    function Serializa : String; override;
    procedure Audita: override;
end;

TLancamentoFiscal : class(TSofreAuditoria)
published
    function Serializa : String; override;
    procedure Audita: override;
end;

TOperadorMatematico : class(TSuperClassePaiDeTodos)
published
    function Serializa : String; override;
end;

Now, with a dozen classes this becomes an extremely confusing process, with a hundred classes is unviable. In addition, your class hierarchy is already getting weird, difficult to understand. At a certain point, you will lose control.

And all this because you are trying to replace the functionality of Interfaces with a hierarchy of classes.

With interfaces you can ensure that objects have some, or some methods in common, that is, follow a contract, without forcing those objects to belong to the same predefined class hierarchy. With interfaces you define capabilities that can be implemented by several uncorrelated classes.

The problem we use of example can be solved simply:

ISerializavel : Interface(IInterface)
published
    function Serializa : String; 
end;

IAuditavel : Interface(IInterface)
published
    procedure Audita;
end;

TUsuario : class(TInterfacedObject, ISerializavel, IAuditavel)
published
    function Serializa : String; 
    procedure Audita;
end;

TLancamentoFiscal : class(TInterfacedObject, ISerializavel, IAuditavel)
published
    function Serializa : String; 
    procedure Audita;
end;

TOperadorMatematico : class(TInterfacedObject, ISerializavel)
published
    function Serializa : String; 
end;

Note that we have removed any and all undesirable relationships between classes. The hierarchy no longer needs to follow a rigid definition. And the introduction of the audit feature did not require changes in the hierarchy, nor its implementation in objects that are not auditable. The Code was also much more readable and maintainable.

As a class can implement more than one interface, this becomes a very efficient way to organize functionalities that are common among objects that have no relation to each other, that is, that have no relation "was" among themselves. Keep your class hierarchy strongly cemented in the object orientation concepts: if an object is not, in fact, from the same family of another, do not make them brothers, or parents and children! Use aggregation or interfaces.

Advantages, disadvantages and applications

Interfaces are a way of specifying a contract (a standardized and expected way of interacting with some object), but classes also define contracts. It is a myth that interfaces are mandatory to specify a contract, as we have seen, classes can define abstract methods, which necessarily need to be implemented in the descendants. It forms a contract.

In my opinion we should first opt for classes, as they are easier to maintain than interfaces. Only when needs arise that are better met by interfaces (such as the example used here) would it introduce interfaces. Often, when introducing interfaces, I create a parent/parent class that provides a standard implementation for such an interface in order to facilitate its implementation (but this does not force that all classes implementing such an interface also inherit from this class: they can implement the interface from scratch).

The book .NET Design Framework Guidelines agrees with my position and provides some more arguments about favoring the use of classes instead of interfaces:

In general classes are the preferred construction to expose abstractions.

The main disadvantage of interfaces is that they are much less than classes concerning the future evolution of an API. Once you publish an interface, your set of members is frozen forever. Any additions to the interface would break types existing that implement that interface.

A class gives us more flexibility. You can add members to classes you have already published. As long as the method is not abstract (or is provided that you provide a standard implementation for the method), any derived classes will continue to function without modifications.

[ . . . ]

One of the most common arguments in favor of interfaces is that they allow separate contract implementation. However, the argument assumes incorrectly that you cannot separate contracts from implementation using classes. Abstract classes residing in a Assembly (a unit, library or a component in Delphi) separate from its concrete implementations is a great way to achieve such separation.

Note: free translation made by me!

The problem of API evolution can be seen in the widespread mess that are the Direct X Apis. You have a lot of interfaces, with the same name, followed by a number only because the interfaces already published could not be modified with the new features introduced to each version of Direct X. Now we have Idirect3d7, Idirect3d8, Idirect3d9, etc.

In the case of Direct X, as interfaces need to be used by different programming languages, it would be very difficult to publish this functionality without resorting to the use of interfaces, but this same problem can happen in our API if we don’t take care to use classes where classes are more pertinent than interfaces!

Therefore, use interfaces when they are the right tool for the case. Every time you have a contract that applies to totally independent (non-correlated) classes the interfaces will be the best solution to the case, preventing you from engulfing your class hierarchy by forcing artificial inheritances.

Also use interfaces when you need to implement multiple inheritance (or something similar) in languages that do not provide support for it.

Also, use interfaces when immutability is a desired effect: you do not want that contract to be modified in the future, but a new contract needs to be signed (a new interface introduced) in case new functionalities arise.

Avoid, at all costs, premature generalization, because this is, like premature optimization, a waste of time most of the time. Design your object structure well, but only generalize when you realize the real need. Remember that it is possible (and desirable) to introduce generalizations during refactoring, and this is even indicated in the paradigms Agile, XP, finally, the most modern.

  • Just to complement, follow the link on Marcos Douglas' blog. His conclusions about patterns are usually totally different from the ones we usually see/read, and make us notice things we usually miss. http://objectpascalprogramming.com/posts/interfaces-em-todo-lugar/

  • 1

    @Victorzanella interesting the link, but strongly disagree with friend Marcos Douglas. My position is to never use interfaces, except when they are the best tool to solve a particular problem. When they are orthogonal functionalities to your class hierarchy (the example I gave in the answer), or when you try to simulate multiple inheritance in languages that do not support the functionality. There are other points to consider as well. I will update the reply with these points. Thank you for the link!

  • It’s nice to see this disagreement, it makes us rethink our concepts. I particularly like the interfaces, and I try to use them whenever possible. It usually avoids several problems. But it’s like you said, each one has a position on it. Thanks :D

  • @Victorzanella updated the answer explaining my view that interfaces should be used when necessary and not always. This fits with the position of . NET’s development team as well. When looking for immutability, yes, interfaces are great ways to implement this. But to force immutability on everything is to prematurely generalize its code. In the end it can end with a code that is difficult to maintain and time-consuming to introduce changes and our area is an area where change is the modus operandi! Hugging.

  • @Victorzanella thanks for the link indication, but I will dispense, because I know well the topics of Marcos and usually disagree a lot of them. The motives are irrelevant at the moment, but know that every time I see an article of his I get deeply annoyed.

  • @Loudenvier, I have noticed that your answer is quite long and it must mean that it contains what I need so that my doubt is resolved. Add that to the fact that you disagree with Macos Douglas and believe that this time I will understand everything or at least get close to it. I won’t be able to read it now, but I will definitely do it and I will give you feedback. Thank you!

  • 2

    @Loudenvier Your reply was excellent. A pity that they thought that this question could be answered as another (which has nothing to do with it). I hope that whoever looks for the answer to this has the patience to read your text, because undoubtedly the correct answer is in it. When you said, "Every time you have a contract that applies to totally independent (non-correlated) classes, interfaces will be the best solution for the case, preventing you from engulfing your class hierarchy by forcing artificial inheritances." I immediately identified my problem

  • @Loudenvier my problem is that I am modifying a component that makes access to database using for this several components of connection (BDE, Firedac, Unidac, etc). Basically I should associate my main component to one of these connection components, but they belong to distinct classes without a common parent. The original solution was to use interfaces to force the implementation of common methods within the context of each of these connection components, i.e., I do not connect these components directly to the main component, but use another component, which I call the connector

  • @Loudenvier Each of these connectors inherits from a common class, which has generic methods that can be used within the context of any connection component (without direct reference to them) while implementing an interface that has insert methods, delete, change and select, which NEED to be implemented within the context of each of the connection components, i.e., the insert method of the Tfiredacconnector class, for example, will exclusively use Firedac to insert into the database

  • @Loudenvier I imagine then that within this my situation the use of interfaces is valid, even when I see that I could have created a common class and inherited from it for each of the connection components. Thinking of "contract" I imagine then that I actually have completely different classes (each of the possible connection components) and because of that I need to manipulate this distinct behavior in mandatory implementations of specific methods. I’d love to discuss this further, but I don’t think this is the best place

  • 2

    @Carlosfeitozafilho in your case I would use a parent class only! With abstract methods. I see these connection classes, all of them, as related to each other. It does not matter that they use (by aggregation) a different object to speak as a database. All have to implement the connection methods and the insertion, deletion, etc. Isn’t it? You can even implement a common treatment in these methods (for example, to call data validation routine, test open connection, etc. all in the base class). In the daughter class just redirects to the correct object of each database.

  • @Loudenvier makes a lot of sense that yes, thanks again!

Show 7 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.