[oop] Interface vs classe de base



Answers

Eh bien, Josh Bloch s'est dit dans Effective Java 2d :

Préférer les interfaces sur les classes abstraites

Quelques points principaux:

  • Les classes existantes peuvent être facilement adaptées pour implémenter une nouvelle interface . Tout ce que vous avez à faire est d'ajouter les méthodes requises si elles n'existent pas encore et ajoutez une clause implements à la déclaration de classe.

  • Les interfaces sont idéales pour définir les mixins . Autrement dit, un mixin est un type qu'une classe peut implémenter en plus de son "type primaire" pour déclarer qu'il fournit un comportement optionnel. Par exemple, Comparable est une interface mixin qui permet à une classe de déclarer que ses instances sont ordonnées par rapport à d'autres objets mutuellement comparables.

  • Les interfaces permettent la construction de frameworks de type non hiérarchique . Les hiérarchies de types sont idéales pour organiser certaines choses, mais d'autres ne tombent pas dans une hiérarchie rigide.

  • Les interfaces permettent des améliorations de fonctionnalités sûres et puissantes via l'idiome de classe wrap-per. Si vous utilisez des classes abstraites pour définir des types, vous quittez le programmeur qui veut ajouter une fonctionnalité sans autre alternative que d'utiliser l'héritage.

De plus, vous pouvez combiner les vertus des interfaces et des classes abstraites en fournissant une classe d'implémentation squelette abstraite pour chaque interface non triviale que vous exportez.

D'un autre côté, les interfaces sont très difficiles à faire évoluer. Si vous ajoutez une méthode à une interface, toutes ses implémentations seront interrompues.

PS .: Achetez le livre. C'est beaucoup plus détaillé.

Question

Quand dois-je utiliser une interface et quand dois-je utiliser une classe de base?

Devrait-il toujours être une interface si je ne veux pas réellement définir une implémentation de base des méthodes?

Si j'ai un cours de chien et chat. Pourquoi voudrais-je implémenter IPet au lieu de PetBase? Je peux comprendre avoir des interfaces pour ISheds ou IBarks (IMakesNoise?), Parce que ceux-ci peuvent être placés sur un animal de compagnie par animal de compagnie, mais je ne comprends pas lequel utiliser pour un animal de compagnie générique.




Regarding C#, in some senses interfaces and abstract classes can be interchangeable. However, the differences are: i) interfaces cannot implement code; ii) because of this, interfaces cannot call further up the stack to subclass; and iii) only can abstract class may be inherited on a class, whereas multiple interfaces may be implemented on a class.




Conceptually, an Interface is used to formally and semi-formally define a set of methods that an object will provide. Formally means a set of method names and signatures, semi-formally means human readable documentation associated with those methods. Interfaces are only descriptions of an API (after all, API stands for Application Programmer Interface ), they can't contain any implementation, and it's not possible to use or run an Interface. They only make explicit the contract of how you should interact with an object.

Classes provide an implementation, they can declare that they implement zero, one or more Interfaces. If a Class is intended to be inherited, the convention is to prefix the Class name with "Base".

There is a distinction between a Base Class and an Abstract Base Classes (ABC). ABCs mix interface and implementation together. Abstract outside of computer programming means "summary", that is "Abstract == Interface". An Abstract Base Class can then describe both an interface, as well as an empty, partial or complete implementation that is intended to be inherited.

Opinions on when to use Interfaces versus Abstract Base Classes versus just Classes is going to vary wildly based on both what you are developing, and which language you are developing in. Interfaces are often associated only with statically typed languages such as Java or C#, but dynamically typed languages can also have Interfaces and Abstract Base Classes. In Python for example, the distinction is made clear between a Class, which declares that it implements an Interface, and an object, which is an instance of a Class, and is said to provide that Interface. It's possible in a dynamic language that two objects that are both instances of the same Class, can declare that they provide completely different interfaces. In Python this is only possible for object attributes, while methods are shared state between all objects of a Class. However in Ruby, objects can have per-instance methods, so it's possible that the Interface between two objects of the same Class can vary as much as the programmer desires (however, Ruby doesn't have any explicit way of declaring Interfaces).

In dynamic languages the Interface to an object is often implicitly assumed, either by introspecting an object and asking it what methods it provides (Look Before You Leap) or preferably by simply attempting to use the desired Interface on an object and catching exceptions if the object doesn't provide that Interface (Easier to Ask Forgiveness than Permission). This can lead to "false positives" where two Interfaces have the same method name but are semantically different, however the trade-off is that your code is more flexible since you don't need to over specify up-front to anticipate all possible uses of your code.




Une différence importante est que vous ne pouvez hériter que d' une classe de base, mais vous pouvez implémenter de nombreuses interfaces. Vous ne voulez donc utiliser qu'une classe de base si vous êtes absolument certain que vous n'aurez pas besoin d'hériter d'une classe de base différente. De plus, si vous trouvez que votre interface est grande, vous devriez commencer à la décomposer en quelques parties logiques qui définissent des fonctionnalités indépendantes, car il n'y a aucune règle que votre classe ne puisse les implémenter toutes (ou que vous pouvez définir une autre interface qui hérite de tous pour les regrouper).




I've found that a pattern of Interface > Abstract > Concrete works in the following use-case:

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

The abstract class defines default shared attributes of the concrete classes, yet enforces the interface. Par exemple:

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

Now, since all mammals have hair and nipples (AFAIK, I'm not a zoologist), we can roll this into the abstract base class

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

And then the concrete classes merely define that they walk upright.

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

This design is nice when there are lots of concrete classes, and you don't want to maintain boilerplate just to program to an interface. If new methods were added to the interface, it would break all of the resulting classes, so you are still getting the advantages of the interface approach.

In this case, the abstract could just as well be concrete; however, the abstract designation helps to emphasize that this pattern is being employed.




Les interfaces et les classes de base représentent deux formes différentes de relations.

L'héritage (classes de base) représente une relation "est-a". Par exemple un chien ou un chat "est-un" animal de compagnie. Cette relation représente toujours le but (unique) de la classe (en conjonction avec le «principe de responsabilité unique» ).

Les interfaces , d'un autre côté, représentent des fonctionnalités supplémentaires d'une classe. Je l'appellerais une relation "est", comme dans " Foo est jetable", d'où l'interface IDisposable en C #.




Interfaces

  • Définit le contrat entre 2 modules. Ne peut avoir aucune implémentation.
  • La plupart des langues vous permettent d'implémenter plusieurs interfaces
  • Modifier une interface est un changement de rupture. Toutes les implémentations doivent être recompilées / modifiées.
  • Tous les membres sont publics. Les implémentations doivent implémenter tous les membres.
  • Les interfaces aident au découplage. Vous pouvez utiliser des cadres fictifs pour simuler quoi que ce soit derrière une interface
  • Les interfaces indiquent normalement une sorte de comportement
  • Les implémentations d'interface sont découplées / isolées les unes des autres

Classes de base

  • Vous permet d'ajouter une implémentation par défaut que vous obtenez gratuitement par dérivation
  • Sauf C ++, vous ne pouvez dériver que d'une classe. Même si cela peut être de plusieurs classes, c'est généralement une mauvaise idée.
  • Changer la classe de base est relativement facile. Les dérivations n'ont pas besoin de faire quelque chose de spécial
  • Les classes de base peuvent déclarer des fonctions protégées et publiques accessibles par des dérivations
  • Résumé Les classes de base ne peuvent pas être facilement manipulées comme des interfaces
  • Les classes de base indiquent normalement la hiérarchie de type (IS A)
  • Les dérivations de classe peuvent dépendre d'un comportement de base (avoir une connaissance complexe de l'implémentation parente). Les choses peuvent être désordonnées si vous faites un changement à la mise en œuvre de base pour un gars et briser les autres.



Use Interfaces to enforce a contract ACROSS families of unrelated classes. For example, you might have common access methods for classes that represent collections, but contain radically different data ie one class might represent a result set from a query, while the other might represent the images in a gallery. Also, you can implement multiple interfaces, thus allowing you to blend (and signify) the capabilities of the class.

Use Inheritance lorsque les classes portent une relation commune et ont donc une signature structurelle et comportementale similaire, c'est-à-dire Car, Moto, Camion et SUV sont tous les types de véhicules routiers qui peuvent contenir un certain nombre de roues, une vitesse de pointe




Prefer interfaces over abstract classes

Rationale, the main points to consider [two already mentioned here] are :

  • Interfaces are more flexible, because a class can implement multiple interfaces. Since Java does not have multiple inheritance, using abstract classes prevents your users from using any other class hierarchy. In general, prefer interfaces when there are no default implementations or state. Java collections offer good examples of this (Map, Set, etc.).
  • Abstract classes have the advantage of allowing better forward compatibility. Once clients use an interface, you cannot change it; if they use an abstract class, you can still add behavior without breaking existing code. If compatibility is a concern, consider using abstract classes.
  • Even if you do have default implementations or internal state, consider offering an interface and an abstract implementation of it . This will assist clients, but still allow them greater freedom if desired [1].
    Of course, the subject has been discussed at length elsewhere [2,3].

[1] It adds more code, of course, but if brevity is your primary concern, you probably should have avoided Java in the first place!

[2] Joshua Bloch, Effective Java, items 16-18.

[3] http://www.codeproject.com/KB/ar ...




J'ai une règle grossière

Fonctionnalité: susceptible d'être différente dans toutes les parties: Interface.

Les données, et la fonctionnalité, les parties seront pour la plupart les mêmes, les parties différentes: la classe abstraite.

Les données et la fonctionnalité fonctionnent réellement, si elles ne sont étendues qu'avec de légères modifications: classe ordinaire (concrète)

Données et fonctionnalités, aucun changement prévu: classe ordinaire (concrète) avec modificateur final.

Données, et peut-être fonctionnalité: lecture seule: membres de l'énumération.

C'est très approximatif et prêt et pas du tout strictement défini, mais il y a un spectre d'interfaces où tout est destiné à être changé en enums où tout est fixé un peu comme un fichier en lecture seule.




Les commentaires précédents à propos de l'utilisation de classes abstraites pour une implémentation commune sont définitivement sur la bonne voie. Un avantage que je n'ai pas encore vu mentionné est que l'utilisation d'interfaces rend beaucoup plus facile la mise en œuvre d'objets fictifs aux fins de tests unitaires. Définir IPet et PetBase comme décrit par Jason Cohen vous permet de vous moquer facilement de différentes conditions de données, sans le surcoût d'une base de données physique (jusqu'à ce que vous décidiez qu'il est temps de tester la vraie chose).




Je recommande d'utiliser la composition au lieu de l'héritage autant que possible. Utilisez des interfaces, mais utilisez des objets membres pour l'implémentation de base. De cette façon, vous pouvez définir une fabrique qui construit vos objets de manière à se comporter d'une certaine manière. Si vous souhaitez modifier le comportement, vous créez une nouvelle méthode d'usine (ou fabrique abstraite) qui crée différents types de sous-objets.

Dans certains cas, vous pouvez constater que vos objets principaux n'ont pas besoin d'interfaces du tout, si tout le comportement mutable est défini dans les objets auxiliaires.

Ainsi, au lieu de IPet ou PetBase, vous pourriez vous retrouver avec un animal de compagnie qui a un paramètre IFurBehavior. Le paramètre IFurBehavior est défini par la méthode CreateDog () de PetFactory. C'est ce paramètre qui est appelé pour la méthode shed ().

Si vous faites cela, vous verrez que votre code est beaucoup plus flexible et la plupart de vos objets simples traitent de comportements très basiques à l'échelle du système.

Je recommande ce modèle même dans les langues à plusieurs héritages.




Juan,

J'aime penser aux interfaces comme un moyen de caractériser une classe. Une classe de race de chien particulière, disons un YorkshireTerrier, peut être une descendante de la classe de chien parent, mais elle implémente aussi IFurry, IStubby, et IYippieDog. Donc la classe définit ce qu'est la classe mais l'interface nous en dit des choses.

L'avantage est que cela me permet, par exemple, de rassembler tous les IYippieDog et de les jeter dans ma collection Ocean. Alors maintenant je peux atteindre un ensemble d'objets particulier et trouver ceux qui répondent aux critères que je regarde sans trop inspecter la classe.

Je trouve que les interfaces devraient vraiment définir un sous-ensemble du comportement public d'une classe. Si elle définit tout le comportement public pour toutes les classes qui l'implémentent, elle n'a généralement pas besoin d'exister. Ils ne me disent rien d'utile.

Cette pensée va cependant à l'encontre de l'idée que chaque classe devrait avoir une interface et que vous devriez coder à l'interface. C'est bien, mais vous vous retrouvez avec beaucoup d'interfaces individuelles vers les classes et cela rend les choses confuses. Je comprends que l'idée est que cela ne coûte vraiment rien à faire et maintenant vous pouvez échanger des choses avec facilité. Cependant, je trouve que je le fais rarement. La plupart du temps, je ne fais que modifier la classe existante et avoir exactement les mêmes problèmes que ceux que j'ai toujours rencontrés si l'interface publique de cette classe doit changer, sauf que je dois maintenant la changer à deux endroits.

Donc, si vous pensez comme moi, vous direz certainement que chat et chien sont IPettable. C'est une caractérisation qui les associe tous les deux.

L'autre partie de ceci est cependant devrait-ils avoir la même classe de base? La question est de savoir s'ils doivent être traités de la même manière. Certes, ils sont tous deux des animaux, mais cela correspond-il à la façon dont nous allons les utiliser ensemble.

Dites que je veux rassembler toutes les classes d'animaux et les mettre dans mon récipient Ark.

Ou ont-ils besoin d'être des mammifères? Peut-être avons-nous besoin d'une sorte d'usine de traite des animaux?

Ont-ils même besoin d'être liés ensemble? Est-ce suffisant de savoir qu'ils sont tous les deux IPettable?

Je ressens souvent le désir de dériver une hiérarchie de classe entière quand j'ai vraiment juste besoin d'une classe. Je le fais en anticipation un jour je pourrais en avoir besoin et d'habitude je ne le fais jamais. Même quand je le fais, je trouve habituellement que je dois faire beaucoup pour le réparer. C'est parce que le premier cours que je crée n'est pas le chien, je ne suis pas chanceux, mais plutôt l'ornithorynque. Maintenant, toute ma hiérarchie de classe est basée sur le cas bizarre et j'ai beaucoup de code gaspillé.

Vous pouvez également trouver à un certain point que tous les chats ne sont pas IPettable (comme celui glabre). Vous pouvez maintenant déplacer cette interface vers toutes les classes dérivées qui correspondent. Vous constaterez qu'un changement beaucoup moins brusque que tout à coup les chats ne sont plus dérivés de PettableBase.




Gardez aussi à l'esprit de ne pas vous perdre dans OO ( voir blog ) et modélisez toujours les objets en fonction du comportement requis, si vous concevez une application dont le seul comportement requis est un nom générique et une espèce pour un animal, alors vous aurez seulement besoin Un animal de classe avec une propriété pour le nom, au lieu de millions de classes pour chaque animal possible dans le monde.




@Joel: Some languages (eg, C++) allow multiple-inheritance.






Links