Ma compréhension actuelle de l'implémentation d'héritage est qu'il ne faut étendre une classe que si une relation IS-A est présente. Si la classe parent peut en outre avoir des types enfants plus spécifiques avec des fonctionnalités différentes, mais partagera des éléments communs résumés dans le parent.
Je remets en question cette compréhension à cause de ce que mon professeur de Java nous recommande de faire. Il a recommandé que pour une JSwing
application que nous construisons en classe
Il faut étendre toutes les JSwing
classes ( JFrame
, JButton
, JTextBox
, etc.) dans des classes personnalisées séparées et spécifiez GUI personnalisation liée à eux (comme la taille des composants, l' étiquette des composants, etc.)
Jusqu'ici, tout va bien, mais il ajoute que chaque JButton devrait avoir sa propre classe étendue personnalisée, même si le seul facteur distinctif est son étiquette.
Par exemple, si l'interface graphique a deux boutons Ok et Annuler . Il recommande de les prolonger comme suit:
class OkayButton extends JButton{
MainUI mui;
public OkayButton(MainUI mui) {
setSize(80,60);
setText("Okay");
this.mui = mui;
mui.add(this);
}
}
class CancelButton extends JButton{
MainUI mui;
public CancelButton(MainUI mui) {
setSize(80,60);
setText("Cancel");
this.mui = mui;
mui.add(this);
}
}
Comme vous pouvez le constater, la seule différence réside dans la setText
fonction.
Alors, est-ce la pratique habituelle?
Btw, le cours où cela a été discuté s'appelle Meilleures pratiques de programmation en Java
[Réponse du Prof]
J'ai donc discuté du problème avec le professeur et soulevé tous les points mentionnés dans les réponses.
Sa justification est que le sous-classement fournit un code réutilisable tout en respectant les normes de conception d'interface graphique. Par exemple , si le développeur a personnalisé utilisé Okay
et des Cancel
boutons dans une fenêtre, il sera plus facile de placer les mêmes boutons dans d' autres Windows ainsi.
Je comprends la raison, je suppose, mais il ne s'agit tout simplement que d'exploiter l' héritage et de fragiliser le code.
Par la suite, tout développeur pourrait appeler le par mégarde setText
sur un Okay
bouton et le modifier. La sous-classe devient simplement gênante dans ce cas.
JButton
et appeler des méthodes publiques dans son constructeur, alors que vous pouvez simplement créerJButton
et appeler ces mêmes méthodes publiques en dehors de la classe?Réponses:
Ceci viole le principe de substitution de Liskov car un objet
OkayButton
ne peut être remplacé dans aucun endroitButton
prévu. Par exemple, vous pouvez modifier l’étiquette de n’importe quel bouton à votre guise. Mais faire cela avec unOkayButton
viole ses invariants internes.C'est un abus classique de l'héritage pour la réutilisation de code. Utilisez plutôt une méthode d'assistance.
Une autre raison de ne pas le faire est qu’il s’agit simplement d’un moyen compliqué d’obtenir la même chose que le code linéaire.
la source
OkayButton
vous avez l'invariant auquel vous songez. LeOkayButton
peut prétendre ne pas avoir d’invariants supplémentaires, il peut simplement considérer le texte comme un défaut et avoir l’intention de supporter pleinement les modifications apportées au texte. Ce n'est toujours pas une bonne idée, mais pas pour cette raison.activateButton(Button btn)
attend un bouton, vous pouvez facilement lui donner une instance deOkayButton
. Le principe ne signifie pas qu'il doit avoir exactement les mêmes fonctionnalités, car cela rendrait l'héritage pratiquement inutile.setText(button, "x")
car cela viole l'invariant (supposé). Il est vrai que cet OkayButton particulier n'a peut-être pas d'invariant intéressant, alors je suppose que j'ai pris un mauvais exemple. Mais ça pourrait. Par exemple, que se passe-t-il si le gestionnaire de clics ditif (myText == "OK") ProcessOK(); else ProcessCancel();
? Ensuite, le texte fait partie de l'invariant.C'est complètement terrible de toutes les manières possibles. Tout au plus , utilisez une fonction usine pour produire des boutons JB. Vous ne devriez en hériter que si vous avez des besoins d'extension importants.
la source
JButton
,JCheckBox
,JRadioButton
, est juste une concession pour les développeurs étant familiers avec d' autres boîtes à outils, comme la AWT plaine, où ces types sont la norme. En principe, ils pourraient tous être gérés par une seule classe de boutons. C'est la combinaison du modèle et du délégué de l'interface utilisateur qui fait la différence.C’est une façon très inhabituelle d’écrire du code Swing. En règle générale, vous ne créez que rarement des sous-classes de composants de l'interface utilisateur Swing, le plus souvent JFrame (afin de configurer des fenêtres enfants et des gestionnaires d'événements), mais même ce sous-classement est inutile et découragé. La personnalisation du texte en boutons et ainsi de suite est généralement effectuée en étendant la classe AbstractAction (ou l' interface Action fournie). Cela peut fournir du texte, des icônes et toute autre personnalisation visuelle nécessaire et les lier au code réel qu’ils représentent. C’est un moyen bien meilleur d’écrire du code d’interface utilisateur que les exemples que vous montrez.
(Au fait, Google Scholar n'a jamais entendu parler du document que vous citez - avez-vous une référence plus précise?)
la source
JPanel
. En fait, il est plus utile de sous-classer celaJFrame
, puisqu'un panneau n'est qu'un conteneur abstrait.IMHO, Les meilleures pratiques de programmation en Java sont définies dans le livre de Joshua Bloch, "Effective Java". C'est bien que votre professeur vous donne des exercices de POO et il est important d'apprendre à lire et à écrire les styles de programmation des autres personnes. Mais en dehors du livre de Josh Bloch, les opinions divergent beaucoup quant aux meilleures pratiques.
Si vous envisagez d'étendre cette classe, vous pouvez également tirer parti de l'héritage. Créez une classe MyButton pour gérer le code commun et sous-classe pour les parties variables:
Quand est-ce une bonne idée? Lorsque vous utilisez les types que vous avez créés! Par exemple, si vous avez une fonction pour créer une fenêtre contextuelle et que la signature est:
Les types que vous venez de créer ne font absolument aucun bien. Mais:
Maintenant, vous avez créé quelque chose d'utile.
Le compilateur vérifie que showPopUp prend un OkButton et un CancelButton. Quelqu'un qui lit le code sait comment cette fonction est censée être utilisée car ce type de documentation provoquera une erreur de compilation si elle devient obsolète. C'est un avantage MAJEUR. Les 1 ou 2 études empiriques sur les avantages de la sécurité de type ont montré que la compréhension du code humain était le seul avantage quantifiable.
Cela évite également les erreurs lorsque vous inversez l'ordre des boutons que vous passez à la fonction. Ces erreurs sont difficiles à repérer, mais elles sont également assez rares. Il s'agit donc d'un avantage MINEUR. Ceci a été utilisé pour vendre la sécurité de type, mais n'a pas été empiriquement prouvé comme étant particulièrement utile.
La première forme de la fonction est plus flexible car elle permet d'afficher deux boutons quelconques. Parfois, cela vaut mieux que de limiter le type de boutons que cela prendra. Rappelez-vous simplement que vous devez tester la fonction any-button dans davantage de circonstances - si cela ne fonctionne que lorsque vous la passez exactement au bon type de boutons, vous ne faites de faveurs à personne en prétendant que cela prendrait n'importe quel type de bouton.
Le problème avec la programmation orientée objet est qu’elle met la charrue avant les boeufs. Vous devez d'abord écrire la fonction pour savoir en quoi consiste la signature et savoir s'il est intéressant de créer des types pour cette signature. Mais en Java, vous devez d’abord créer vos types pour pouvoir écrire la signature de la fonction.
Pour cette raison, vous pouvez écrire du code Clojure pour voir à quel point il peut être génial d'écrire vos fonctions en premier. Vous pouvez coder rapidement, en pensant autant que possible à la complexité asymptotique, et l'hypothèse de l'immuabilité de Clojure évite les bogues autant que les types le font en Java.
Je suis toujours un fan des types statiques pour les grands projets et utilise maintenant des utilitaires fonctionnels qui me permettent d' écrire des fonctions en premier et de nommer mes types plus tard en Java . Juste une pensée.
PS J'ai fait le
mui
pointeur final - il n'a pas besoin d'être mutable.la source
Je serais prêt à parier que votre professeur ne croit pas réellement que vous devriez toujours utiliser un composant Swing pour l'utiliser. Je parie qu'ils ne font que citer cet exemple pour vous forcer à vous entraîner à prolonger les cours. Je ne m'inquiéterais pas trop des meilleures pratiques du monde réel pour l'instant.
Cela dit, dans le monde réel, nous privilégions la composition au détriment de l'héritage .
Votre règle "on ne devrait étendre une classe que si une relation IS-A est présente" est incomplète. Il devrait se terminer par "... et nous devons changer le comportement par défaut de la classe" en gros caractères gras.
Vos exemples ne correspondent pas à ce critère. Vous n'avez pas à
extend
laJButton
classe juste pour définir son texte. Vous n'êtes pas obligé de simplement ajouter des composants àextend
laJFrame
classe. Vous pouvez très bien faire ces choses en utilisant leurs implémentations par défaut. Ajouter l'héritage, c'est donc ajouter des complications inutiles.Si je vois une classe dans
extends
une autre classe, je vais me demander ce que cette classe est en train de changer . Si vous ne changez rien, ne me faites pas du tout regarder la classe.Retour à votre question: quand devriez-vous étendre une classe Java? Lorsque vous avez vraiment une très bonne raison de prolonger un cours.
Voici un exemple spécifique: une façon de faire une peinture personnalisée (pour un jeu ou une animation, ou simplement pour un composant personnalisé) consiste à étendre la
JPanel
classe. (Plus d'informations à ce sujet ici .) Vous étendez laJPanel
classe parce que vous devez remplacer lapaintComponent()
fonction. En fait, vous changez le comportement de la classe . Vous ne pouvez pas faire de peinture personnalisée avec l'JPanel
implémentation par défaut .Mais comme je l'ai dit, votre professeur utilise probablement ces exemples comme excuse pour vous forcer à vous entraîner à prolonger les cours.
la source
Border
qui peint d'autres décorations. Ou créez uneJLabel
et passez uneIcon
implémentation personnalisée . Il n'est pas nécessaire de sous-classerJPanel
pour la peinture (et pourquoiJPanel
au lieu deJComponent
).