Pour les classes avec des champs facultatifs, est-il préférable d'utiliser l'héritage ou une propriété nullable? Considérez cet exemple:
class Book {
private String name;
}
class BookWithColor extends Book {
private String color;
}
ou
class Book {
private String name;
private String color;
//when this is null then it is "Book" otherwise "BookWithColor"
}
ou
class Book {
private String name;
private Optional<String> color;
//when isPresent() is false it is "Book" otherwise "BookWithColor"
}
Le code dépendant de ces 3 options serait:
if (book instanceof BookWithColor) { ((BookWithColor)book).getColor(); }
ou
if (book.getColor() != null) { book.getColor(); }
ou
if (book.getColor().isPresent()) { book.getColor(); }
La première approche me semble plus naturelle, mais peut-être est-elle moins lisible à cause de la nécessité de faire du casting. Existe-t-il un autre moyen d'obtenir ce comportement?
java
inheritance
class
null
Bojan VukasovicTest
la source
la source
Réponses:
Cela dépend des circonstances. L'exemple spécifique est irréaliste car vous n'auriez pas de sous-classe appelée
BookWithColor
dans un vrai programme.Mais en général, une propriété qui n'a de sens que pour certaines sous-classes ne devrait exister que dans ces sous-classes.
Par exemple, si
Book
hasPhysicalBook
etDigialBook
as descendants, alorsPhysicalBook
peut avoir uneweight
propriété etDigitalBook
unesizeInKb
propriété. MaisDigitalBook
n'aura pasweight
et vice versa.Book
n'aura aucune propriété, car une classe ne devrait avoir que des propriétés partagées par tous les descendants.Un meilleur exemple est de regarder les classes de vrais logiciels. Le
JSlider
composant a le champmajorTickSpacing
. Étant donné que seul un curseur a des "ticks", ce champ n'a de sens que pourJSlider
ses descendants. Ce serait très déroutant si d'autres composants frères commeJButton
avaient unmajorTickSpacing
champ.la source
Un point important qui ne semble pas avoir été mentionné: dans la plupart des langues, les instances d'une classe ne peuvent pas changer de quelle classe elles sont des instances. Donc, si vous aviez un livre sans couleur et que vous vouliez ajouter une couleur, vous auriez besoin de créer un nouvel objet si vous utilisez différentes classes. Et puis vous devrez probablement remplacer toutes les références à l'ancien objet par des références au nouvel objet.
Si les "livres sans couleur" et les "livres avec couleur" sont des instances de la même classe, l'ajout ou la suppression de la couleur sera alors beaucoup moins problématique. (Si votre interface utilisateur affiche une liste de "livres avec couleur" et "livres sans couleur", alors cette interface utilisateur devra évidemment changer, mais je m'attends à ce que ce soit quelque chose que vous devez gérer de toute façon, semblable à une "liste de rouge"). livres "et" liste de livres verts ").
la source
Considérez un JavaBean (comme le vôtre
Book
) comme un enregistrement dans une base de données. Les colonnes facultatives sontnull
lorsqu'elles n'ont aucune valeur, mais c'est parfaitement légal. Par conséquent, votre deuxième option:Est le plus raisonnable. 1
Soyez prudent cependant avec la façon dont vous utilisez la
Optional
classe. Par exemple, ce n'est pas le casSerializable
, ce qui est généralement une caractéristique d'un JavaBean. Voici quelques conseils de Stephen Colebourne :Par conséquent, dans votre classe, vous devez utiliser
null
pour représenter que le champ n'est pas présent, mais lorsque lecolor
quitte leBook
(en tant que type de retour), il doit être entouré d'unOptional
.Cela fournit une conception claire et un code plus lisible.
1 Si vous avez l' intention d'un
BookWithColor
pour encapsuler une « classe » de l' ensemble des livres qui ont des capacités spécialisées dans d' autres livres, alors il serait logique d'héritage d'utilisation.la source
Optional
classe à Java dans ce but précis. Ce n'est peut-être pas OO, mais c'est certainement une opinion valable.Vous devez soit utiliser une interface (pas d'héritage) ou une sorte de mécanisme de propriété (peut-être même quelque chose qui fonctionne comme le cadre de description des ressources).
Donc soit
ou
Clarification (modifier):
Ajouter simplement des champs facultatifs dans la classe d'entité
Surtout avec le wrapper facultatif(modifier: voir la réponse de 4castle ) Je pense que cela (en ajoutant des champs dans l'entité d'origine) est un moyen viable d'ajouter de nouvelles propriétés à petite échelle. Le plus gros problème avec cette approche est qu'elle pourrait fonctionner contre un faible couplage.Imaginez que votre classe de livres soit définie dans un projet dédié à votre modèle de domaine. Vous ajoutez maintenant un autre projet qui utilise le modèle de domaine pour effectuer une tâche spéciale. Cette tâche nécessite une propriété supplémentaire dans la classe de livres. Soit vous vous retrouvez avec l'héritage (voir ci-dessous), soit vous devez changer le modèle de domaine commun pour rendre la nouvelle tâche possible. Dans ce dernier cas, vous pouvez vous retrouver avec un tas de projets qui dépendent tous de leurs propres propriétés ajoutées à la classe de livre tandis que la classe de livre elle-même dépend en quelque sorte de ces projets, car vous ne pouvez pas comprendre la classe de livre sans le projets supplémentaires.
Pourquoi l'héritage est-il problématique lorsqu'il s'agit de fournir des propriétés supplémentaires?
Quand je vois votre exemple de classe "Book", je pense à un objet de domaine qui finit souvent par avoir de nombreux champs et sous-types facultatifs. Imaginez simplement que vous souhaitez ajouter une propriété pour les livres qui incluent un CD. Il existe maintenant quatre types de livres: livres, livres avec couleur, livres avec CD, livres avec couleur et CD. Vous ne pouvez pas décrire cette situation avec héritage en Java.
Avec les interfaces, vous contournez ce problème. Vous pouvez facilement composer les propriétés d'une classe de livres spécifique avec des interfaces. La délégation et la composition vous permettront d'obtenir facilement la classe souhaitée. Avec l'héritage, vous vous retrouvez généralement avec des propriétés facultatives dans la classe qui ne sont là que parce qu'une classe soeur en a besoin.
Pour en savoir plus, pourquoi l'héritage est souvent une idée problématique:
Pourquoi l'héritage est-il généralement considéré comme une mauvaise chose par les partisans de la POO
JavaWorld: Pourquoi s'étendre est mal
Le problème avec l'implémentation d'un ensemble d'interfaces pour composer un ensemble de propriétés
Lorsque vous utilisez des interfaces pour l'extension, tout va bien tant que vous n'en avez qu'un petit ensemble. Surtout lorsque votre modèle d'objet est utilisé et étendu par d'autres développeurs, par exemple dans votre entreprise, la quantité d'interfaces augmentera. Et finalement, vous finissez par créer une nouvelle "propriété-interface" officielle qui ajoute une méthode que vos collègues ont déjà utilisée dans leur projet pour le client X pour un cas d'utilisation totalement indépendant - Ugh.
edit: Un autre aspect important est mentionné par gnasher729 . Vous souhaitez souvent ajouter dynamiquement des propriétés facultatives à un objet existant. Avec l'héritage ou les interfaces, vous devrez recréer l'objet entier avec une autre classe qui est loin d'être facultative.
Lorsque vous vous attendez à des extensions de votre modèle d'objet dans une telle mesure, vous serez mieux en modélisant explicitement la possibilité d' extension dynamique . Je propose quelque chose comme ci-dessus où chaque "extension" (dans ce cas la propriété) a sa propre classe. La classe fonctionne comme espace de noms et identifiant pour l'extension. Lorsque vous définissez les conventions de dénomination des packages en conséquence, cette méthode autorise une quantité infinie d'extensions sans polluer l'espace de noms pour les méthodes de la classe d'entité d'origine.
Dans le développement de jeux, vous rencontrez souvent des situations où vous souhaitez composer le comportement et les données dans de nombreuses variantes. C'est pourquoi le système entité-composant de modèle d'architecture est devenu très populaire dans les cercles de développement de jeux. C'est également une approche intéressante que vous voudrez peut-être examiner lorsque vous attendez de nombreuses extensions de votre modèle d'objet.
la source
PropertiesHolder
serait probablement une classe abstraite. Mais que proposez-vous comme une implémentation pourgetProperty
ougetPropertyValue
? Les données doivent encore être stockées dans une sorte de champ d'instance quelque part. Ou utiliseriez-vous unCollection<Property>
pour stocker les propriétés au lieu d'utiliser des champs?Book
extensionPropertiesHolder
pour des raisons de clarté. LePropertiesHolder
devrait généralement être membre de toutes les classes qui ont besoin de cette fonctionnalité. L'implémentation contiendrait unMap<Class<? extends Property<?>>,Property<?>>
ou quelque chose comme ça. C'est la partie laide de l'implémentation mais elle fournit un cadre vraiment sympa qui fonctionne même avec JAXB et JPA.Si la
Book
classe est dérivable sans propriété de couleur comme ci-dessousalors l'héritage est raisonnable.
la source
color
est privé, aucune classe dérivée ne devrait decolor
toute façon être concernée . Ajoutez simplement quelques constructeurs àBook
cela ignorercolor
complètement et ce sera par défautnull
.