Dans le langage Java, la classe abstraite et l'interface sont deux mécanismes qui prennent en charge la définition de classe abstraite. C’est précisément grâce à l’existence de ces deux mécanismes que Java dispose de puissantes capacités orientées objet. Il existe de grandes similitudes entre la classe abstraite et l'interface en termes de prise en charge de la définition de classe abstraite, et elles peuvent même être remplacées les unes par les autres. Par conséquent, de nombreux développeurs semblent plus décontractés quant au choix de la classe abstraite et de l'interface lors de la définition des classes abstraites. En fait, il existe encore une grande différence entre les deux. Leur choix reflète même la compréhension de la nature du domaine problématique et la question de savoir si la compréhension de l'intention de conception est correcte et raisonnable. Cet article analysera les différences entre eux et tentera de fournir aux développeurs une base pour choisir entre les deux.
Comprendre les classes abstraites
La classe abstraite et l'interface sont toutes deux utilisées pour définir des classes abstraites dans le langage Java (la classe abstraite dans cet article n'est pas traduite de la classe abstraite, elle représente un corps abstrait et la classe abstraite est utilisée pour définir des classes abstraites dans le langage Java) A méthode, veuillez faire attention à la distinction), alors qu'est-ce qu'une classe abstraite et quels avantages l'utilisation de classes abstraites peut-elle nous apporter ?
Dans le concept orienté objet, nous savons que tous les objets sont représentés par des classes, mais l’inverse n’est pas vrai. Toutes les classes ne sont pas utilisées pour décrire des objets. Si une classe ne contient pas suffisamment d'informations pour décrire un objet spécifique, une telle classe est une classe abstraite. Les classes abstraites sont souvent utilisées pour représenter des concepts abstraits que nous dérivons de l'analyse et de la conception de domaines problématiques. Ce sont des abstractions d'une série de concepts spécifiques qui semblent différents mais qui sont essentiellement les mêmes. Par exemple : si nous développons un logiciel d'édition graphique, nous constaterons qu'il existe des concepts spécifiques tels que les cercles et les triangles dans le domaine problématique. Ils sont différents, mais ils appartiennent tous au concept de forme. Le concept de forme est différent. dans le domaine problématique. S’il existe, c’est un concept abstrait. C'est précisément parce que les concepts abstraits n'ont pas de concepts concrets correspondants dans le domaine du problème, de sorte que les classes abstraites utilisées pour représenter les concepts abstraits ne peuvent pas être instanciées.
Dans le domaine orienté objet, les classes abstraites sont principalement utilisées pour masquer les types. Nous pouvons construire une description abstraite d’un ensemble fixe de comportements, mais cet ensemble de comportements peut avoir un certain nombre d’implémentations concrètes possibles. Cette description abstraite est la classe abstraite, et cet ensemble de toutes les implémentations concrètes possibles est représenté par toutes les classes dérivées possibles. Les modules peuvent fonctionner sur un corps abstrait. Parce qu'un module repose sur une abstraction fixe, il ne peut pas être modifié en même temps, le comportement de ce module peut également être étendu en dérivant de cette abstraction. Les lecteurs familiers avec OCP doivent savoir que pour réaliser l'OCP (Open-Closed Principe), l'un des principes fondamentaux de la conception orientée objet, les classes abstraites sont la clé.
Examiner la classe abstraite et l'interface du niveau de définition grammaticale
Au niveau grammatical, le langage Java propose différentes méthodes de définition pour la classe abstraite et l'interface. Voici un exemple de définition d'une classe abstraite nommée Demo pour illustrer cette différence.
La façon de définir la classe abstraite Demo à l’aide de la classe abstraite est la suivante :
Démo de classe abstraite{
méthode vide abstraite1();
méthode vide abstraite2();
…
}
La façon de définir la classe abstraite Demo à l’aide de l’interface est la suivante :
Démo de l'interface{
méthode void1();
méthode void2();
…
}
Dans la méthode de classe abstraite, Demo peut avoir ses propres données membres ou des méthodes membres non abstraites. Dans la méthode d'interface, Demo ne peut avoir que des données membres statiques qui ne peuvent pas être modifiées (c'est-à-dire qu'il doit s'agir de static final , mais de données). les membres ne sont généralement pas définis dans l'interface) et toutes les méthodes membres sont abstraites. Dans un sens, l’interface est une forme particulière de classe abstraite.
D'un point de vue programmation, la classe abstraite et l'interface peuvent être utilisées pour mettre en œuvre l'idée de « conception par contrat ». Cependant, il existe encore quelques différences dans les utilisations spécifiques.
Tout d'abord, la classe abstraite représente une relation d'héritage dans le langage Java, et une classe ne peut utiliser la relation d'héritage qu'une seule fois (car Java ne prend pas en charge l'héritage multiple - note de transfert). Cependant, une classe peut implémenter plusieurs interfaces. Il s'agit peut-être d'un compromis pris en compte par les concepteurs du langage Java lorsqu'ils envisagent la prise en charge par Java de l'héritage multiple.
Deuxièmement, dans la définition de la classe abstraite, nous pouvons attribuer le comportement par défaut de la méthode. Mais dans la définition de l'interface, les méthodes ne peuvent pas avoir de comportement par défaut. Afin de contourner cette restriction, des délégués doivent être utilisés, mais cela ajoutera une certaine complexité et causera parfois beaucoup de problèmes.
Il existe un autre problème sérieux lié à l'impossibilité de définir un comportement par défaut dans une classe abstraite, à savoir que cela peut entraîner des problèmes de maintenance. Parce que si vous souhaitez ultérieurement modifier l'interface de la classe (généralement représentée par une classe abstraite ou une interface) pour l'adapter à de nouvelles situations (par exemple, ajouter de nouvelles méthodes ou ajouter de nouveaux paramètres à des méthodes déjà utilisées), ce sera très gênant. peut prendre beaucoup de temps (surtout s'il existe de nombreuses classes dérivées). Mais si l'interface est implémentée via une classe abstraite, il vous suffira peut-être de modifier le comportement par défaut défini dans la classe abstraite.
De même, si le comportement par défaut ne peut pas être défini dans une classe abstraite, la même implémentation de méthode apparaîtra dans chaque classe dérivée de la classe abstraite, violant le principe « une règle, un endroit », entraînant une duplication de code, ce qui est également préjudiciable à l'entretien. Soyez donc très prudent lorsque vous choisissez entre une classe abstraite et une interface.
Examiner la classe abstraite et l'interface du niveau du concept de conception
Ce qui précède traite principalement de la différence entre la classe abstraite et l'interface du point de vue de la définition grammaticale et de la programmation. Les différences à ces niveaux sont relativement faibles et non essentielles. Cette section analysera la différence entre classe abstraite et interface à un autre niveau : les concepts de conception reflétés dans les deux. L'auteur estime que ce n'est qu'en analysant à ce niveau que nous pourrons comprendre l'essence des deux concepts.
Comme mentionné précédemment, la classe abstraite incarne une relation d'héritage dans le langage Java. Afin de rendre la relation d'héritage raisonnable, il doit y avoir une relation « est-un » entre la classe parent et la classe dérivée, c'est-à-dire la classe parent et. la classe dérivée a le même concept, cela devrait essentiellement être le même. Ce n'est pas le cas pour les interfaces. Il n'est pas nécessaire que l'implémenteur de l'interface et la définition de l'interface soient conceptuellement cohérents, mais qu'ils implémentent uniquement le contrat défini par l'interface. Afin de rendre la discussion plus facile à comprendre, un exemple simple sera illustré ci-dessous.
Prenons un tel exemple. Supposons qu'il existe un concept abstrait sur la porte dans notre domaine problématique. La porte a deux actions : ouvrir et fermer. À ce stade, nous pouvons définir un type qui représente le concept abstrait via une classe ou une interface abstraite. les méthodes sont les suivantes :
Utilisez une classe abstraite pour définir Door :
porte de classe abstraite{
vide abstrait open();
résumé vide fermer();
}
Définir la porte à l'aide de la méthode d'interface :
interface porte{
vide ouvert();
annuler fermer();
}
D'autres types de portes spécifiques peuvent étendre la porte définie à l'aide de la méthode de classe abstraite ou implémenter la porte définie à l'aide de la méthode d'interface. Il semble qu'il n'y ait pas de grande différence entre l'utilisation d'une classe abstraite et d'une interface.
Si la porte doit maintenant avoir une fonction d'alarme. Comment devrions-nous concevoir la structure de classe pour cet exemple (dans cet exemple, il s'agit principalement de montrer la différence dans les concepts de conception entre la classe abstraite et l'interface, et d'autres problèmes non pertinents ont été simplifiés ou ignorés) ? Les solutions possibles sont répertoriées ci-dessous et ces différentes options sont analysées au niveau du concept de conception.
Première solution :
Ajoutez simplement une méthode d'alarme à la définition de Porte, comme suit :
porte de classe abstraite{
vide abstrait open();
résumé vide fermer();
alarme vide abstraite ();
}
ou
interface porte{
vide ouvert();
annuler fermer();
annuler l'alarme ();
}
Ensuite, la AlarmDoor avec fonction d'alarme est définie comme suit :
la classe AlarmDoor étend la porte{
vide ouvert(){…}
annuler fermer(){…}
annuler l'alarme(){…}
}
ou
la classe AlarmDoor implémente Door{
vide ouvert(){…}
annuler fermer(){…}
annuler l'alarme(){…}
}
Cette méthode viole l'ISP (Interface Segregation Principe), un principe fondamental de la conception orientée objet. Dans la définition de Door, la méthode de comportement inhérente au concept Door lui-même est mélangée à la méthode de comportement d'un autre concept « alarme ». Un problème causé par cela est que les modules qui s'appuient uniquement sur le concept de porte changeront en raison des changements dans le concept « d'alarme » (par exemple, en modifiant les paramètres de la méthode d'alarme), et vice versa.
Deuxième solution :
Etant donné que l'ouverture, la fermeture et l'alarme appartiennent à deux concepts différents, selon le principe ISP, ils doivent être définis dans des classes abstraites représentant ces deux concepts. Les méthodes de définition sont les suivantes : les deux concepts sont définis à l'aide de la méthode de classe abstraite ; les deux concepts sont définis à l'aide de la méthode d'interface ; un concept est défini à l'aide de la méthode de classe abstraite et l'autre concept est défini à l'aide de la méthode d'interface.
Évidemment, puisque le langage Java ne prend pas en charge l’héritage multiple, il n’est pas possible de définir les deux concepts à l’aide d’une classe abstraite. Les deux dernières méthodes sont toutes deux réalisables, mais leur choix reflète la compréhension de l'essence du concept dans le domaine du problème et si le reflet de l'intention de conception est correct et raisonnable. Analysons et expliquons un par un.
Si les deux concepts sont définis à l'aide de la méthode d'interface, cela reflète deux problèmes : 1. Nous ne comprenons peut-être pas clairement le domaine du problème. AlarmDoor est-il essentiellement une porte ou une alarme ? 2. S'il n'y a aucun problème avec notre compréhension du domaine problématique, par exemple : grâce à l'analyse du domaine problématique, nous constatons qu'AlarmDoor est conceptuellement cohérent avec Door, alors nous ne serons pas en mesure de révéler correctement notre intention de conception lors de sa mise en œuvre. , car Les définitions de ces deux concepts (tous deux définis à l'aide de la méthode d'interface) ne reflètent pas le sens ci-dessus.
Si notre compréhension du domaine problématique est la suivante : AlarmDoor est conceptuellement essentiellement une porte, et elle a également pour fonction d'alarmer. Comment devrions-nous le concevoir et le mettre en œuvre pour refléter clairement notre sens ? Comme mentionné précédemment, la classe abstraite représente une relation d'héritage dans le langage Java, et la relation d'héritage est essentiellement une relation « est-un ». Donc pour le concept de Door, nous devrions utiliser la méthode de classe abstraite pour le définir. De plus, AlarmDoor dispose d'une fonction d'alarme, ce qui signifie qu'il peut compléter le comportement défini dans le concept d'alarme, de sorte que le concept d'alarme peut être défini via l'interface. Comme indiqué ci-dessous :
porte de classe abstraite{
vide abstrait open();
résumé vide fermer();
}
interfaceAlarme{
annuler l'alarme ();
}
classe Alarme La porte étend la porte met en œuvre l'alarme {
vide ouvert(){…}
annuler fermer(){…}
annuler l'alarme(){…}
}
Cette méthode de mise en œuvre peut fondamentalement refléter clairement notre compréhension du domaine du problème et révéler correctement nos intentions de conception. En fait, la classe abstraite représente la relation "est-un", et l'interface représente la relation "comme-un". Vous pouvez l'utiliser comme base lors du choix. Bien sûr, cela est basé sur la compréhension du domaine du problème. Exemple : Si AlarmDoor est essentiellement un concept d'alarme et a la fonction d'une porte, alors la définition ci-dessus doit être inversée.
résumé
1.La classe abstraite représente une relation d'héritage dans le langage Java, et une classe ne peut utiliser la relation d'héritage qu'une seule fois. Cependant, une classe peut implémenter plusieurs interfaces.
2. Dans la classe abstraite, vous pouvez avoir vos propres données membres, ainsi que des méthodes membres non abstraites, mais dans l'interface, vous ne pouvez avoir que des données membres statiques qui ne peuvent pas être modifiées (c'est-à-dire qu'elles doivent être statiques finales, mais dans l'interface, les membres de données ne sont généralement pas définis) et toutes les méthodes membres sont abstraites.
3. La classe abstraite et l'interface reflètent différents concepts de conception. En fait, la classe abstraite représente la relation « est-un » et l'interface représente la relation « comme-un ».
4. Les classes qui implémentent des classes abstraites et des interfaces doivent implémenter toutes les méthodes qu'elles contiennent. Les classes abstraites peuvent avoir des méthodes non abstraites. Il ne peut pas y avoir de méthodes d'implémentation dans l'interface.
5. Les variables définies dans l'interface sont publiques static final par défaut et leur valeur initiale doit être donnée, elles ne peuvent donc pas être redéfinies dans la classe d'implémentation, ni leurs valeurs ne peuvent être modifiées.
6. Les variables des classes abstraites sont conviviales par défaut et leurs valeurs peuvent être redéfinies dans les sous-classes ou réaffectées.
7. Les méthodes de l'interface sont de type public et abstrait par défaut.
en conclusion
La classe abstraite et l'interface sont deux manières de définir des classes abstraites dans le langage Java, et elles sont très similaires. Cependant, leur sélection reflète souvent la compréhension de l'essence du concept dans le domaine du problème et si le reflet de l'intention de conception est correct et raisonnable, car ils expriment des relations différentes entre les concepts (bien qu'ils puissent tous remplir les fonctions requises). Il s'agit en fait d'une sorte d'utilisation idiomatique du langage, j'espère que les lecteurs pourront le comprendre attentivement.