Not everything that is done with interfaces, it is possible to be done with classes. An interface is a contract, does not imply any implementation, already a class implies an implementation, even if it is possible to use 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, necessarily would have to belong to the same class hierarchy.Generally speaking:He is implies a relationship "being"(Functionary) It is a Person)Interface implies a relationship "can"Bitmap can be dislocated)I will answer without implementation code (and perhaps with a syntax not 100%) to avoid limiting this response to Delphi, since your question is of general use for Object Guidance.Imagine that you want to implement a standardized form for class serialization for JSON (or any other format). If you choose the path of inheritance you will have to force all objects to inherit from the same parent. This can be undesirable, because it will introduce a coupling between classes with no between each other. Your object hierarchy will no longer be pure, we can say. With classes you would 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 (a lot less a mathematical operator that you use, for example, to interpret dynamic expressions created by the user), but to use 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 would not be so serious, since we want all to be serializable. But what if we need some behavior that is common among some classes, but not in others (imagine that you have hundreds of classes, divided into several hierarchies, all inheriting from the parent class). What would you do in that case? You would go there in the "father of all" class and add an abstract method. The cascade effect of this is huge: all its hundreds of classes would have to implement this new methodeven those who don't need it.Imagine that we want to add, for example, audit functionality, which is common to various objects, but not all. For example, it makes sense for a User and a Accounting Launch to be audited, but never a Mathematical Operator. How to solve this elegantly? You could simply make an empty implementation of the audit method, but it already starts sounding like a gambiarra. And it may still have unanticipated effects if the code that calls the audit method is expecting some specific behavior of those who implement this contract/interface/function, such as updating some counter, allocating some memory area, etc. You end up having an object implements the method of auditing in theory, but not in fact. Sounds bad, doesn't it?You could try to modify the class hierarchy to figure out a way to put a common parent only among the 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 it is inviable. In addition, its class hierarchy already begins to get weird, difficult to understand. At a certain time, you will lose control. And all this because you are trying to replace the functionality of Interfaces with a class hierarchy.With interfaces you can ensure that objects have any, or some common methods, i.e. follow a contract, without forcing these objects to belong to the same predefined class hierarchy. With interfaces you define capabilities that can be implemented by several unrelated classes.The problem we use for 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 remove any undesirable relationship between classes. The hierarchy no longer needs to follow a rigid definition. And the introduction of audit functionality 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 features that are common among objects that have no relationship with each other, that is, that have no relationship "being"with each other. Keep your class hierarchy strongly cemented in the concepts of object orientation: if an object is not of the same family of another, do not make them brothers, or parents and children! Use aggregation or interfaces.Advantages, disadvantages and applicationsInterfaces 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 descendants. That's a contract. In my opinion we must first opt for classes, since they are easier to maintain than interfaces. Only when needs arise that are better met by interfaces (such as the example used here) would introduce interfaces. Often, by introducing interfaces, I create a parent class that provides a standard implementation for such an interface in order to facilitate its implementation (but this does not force all classes that implement such an interface also inherit from this class: they can implement the interface from scratch).The book https://stackoverflow.com/questions/5816563/when-should-i-choose-inheritance-over-an-interface-when-designing-c-sharp-class agrees with my positioning and provides some more arguments about favoring the use of classes in place of interfaces:In general classes are the preferred construction to expose abstractions.The main disadvantage of interfaces is that they are much less
flexible than classes in what concerns the future evolution of an API.
Once you publish an interface, your membership set 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
be, provided you provide a standard implementation for the method),
any derived classes will continue to work without modifications.[ . . . ]One of the most common arguments in favor of interfaces is that they
allow to separate implementation contract. However, the argument
incorrectly assumes that you cannot separate contracts from
implementation using classes. Abstract classes residing in one
assembly (a Unit, library or a Delphi component) separate from
its concrete implementations is a great way to achieve this
separation.Note: free translation made by me!The issue 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 already published interfaces could not be modified with the new features introduced to each Direct X version. 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 an API of ours if we do not take care of using 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 classes (not correlates) the interfaces will be the best solution for the case, preventing you from ingesting your class hierarchy by forcing artificial inheritances. Use interfaces also when you need to implement multiple inheritance (or something similar) in languages that do not support it.Also use interfaces when immutability is a desired effect: you don't want that contract to be modified in the future, but a new contract needs to be signed (a new interface introduced) in case new features arise.Avoid premature generalization at all costs, as this is, such as 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 refactorings, and this, even is indicated in the Agile, XP paradigms, in short, the most modern.