Comment implémenter l'héritage RealNumber et ComplexNumber?

11

Si tout va bien pas trop académique ...

Disons que j'ai besoin de nombres réels et complexes dans ma bibliothèque SW.

Basé sur la relation is-a (ou ici ), le nombre réel est un nombre complexe, où b dans la partie imaginaire du nombre complexe est simplement 0.

D'un autre côté, mon implémentation serait que cet enfant étend le parent, donc dans le parent RealNumber, j'aurais une partie réelle et l'enfant ComplexNumber ajouterait de l'art imaginaire.

Il y a aussi une opinion, que l' héritage est mauvais .

Je me souviens comme hier, quand j'apprenais la POO à l'université, mon professeur a dit, ce n'est pas un bon exemple d'héritage car la valeur absolue de ces deux est calculée différemment (mais pour cela nous avons une surcharge de méthode / polymorfisme, non?) .. .

Mon expérience est que nous utilisons souvent l'héritage pour résoudre DRY, par conséquent, nous avons souvent des classes abstraites artificielles dans la hiérarchie (nous avons souvent du mal à trouver des noms car ils ne représentent pas des objets d'un monde réel).

Betlista
la source
7
cela ressemble à la question précédente: le rectangle devrait
moucher
1
@gnat Oh mec, c'était un autre exemple que je voulais utiliser ... Merci!
Betlista
7
... Notez que la phrase "le nombre réel est un nombre complexe" au sens mathématique n'est valable que pour les nombres immuables , donc si vous utilisez des objets immuables, vous pouvez éviter la violation LSP (il en va de même pour les carrés et les rectangles, voir ceci SO réponse ).
Doc Brown
5
... Notez en outre que le calcul de la valeur absolue pour les nombres complexes fonctionne également pour les nombres réels, donc je ne suis pas sûr de ce que votre professeur voulait dire. Si vous implémentez une méthode "Abs ()" correctement dans un nombre complexe immuable et en dérivez un "réel", la méthode Abs () fournira toujours des résultats corrects.
Doc Brown
3
Le double possible de Rectangle devrait
BobDalgleish

Réponses:

17

Même si dans un sens mathématique, un nombre réel est un nombre complexe, ce n'est pas une bonne idée de dériver le réel du complexe. Il viole le principe de substitution de Liskov en disant (entre autres) qu'une classe dérivée ne doit pas cacher les propriétés d'une classe de base.

Dans ce cas, un nombre réel devrait masquer la partie imaginaire du nombre complexe. Il est clair que cela n'a aucun sens de stocker un nombre à virgule flottante caché (partie imaginaire) si vous n'avez besoin que de la partie réelle.

Il s'agit essentiellement du même problème que l'exemple rectangle / carré mentionné dans un commentaire.

Frank Puffer
la source
2
Aujourd'hui, j'ai vu ce "principe de substitution de Liskow" à plusieurs reprises, je vais devoir en lire plus à ce sujet, car je ne le sais pas.
Betlista
7
Il est tout à fait correct de signaler la partie imaginaire d'un nombre réel comme zéro, par exemple par une méthode en lecture seule. Mais cela n'a aucun sens d'implémenter un réel comme un nombre complexe où la partie imaginaire est mise à zéro. C'est exactement un cas où l'héritage est trompeur: alors que l'héritage d'interface serait sans doute bien ici, l'héritage d'implémentation entraînerait une conception problématique.
amon
4
Il est parfaitement logique que les nombres réels héritent des nombres complexes, tant que les deux sont immuables. Et cela ne vous dérange pas les frais généraux.
Déduplicateur
@Deduplicator: Point intéressant. L'immuabilité résout de nombreux problèmes, mais je ne suis pas encore entièrement convaincu dans ce cas. Faut y penser.
Frank Puffer
3

pas un bon exemple d'héritage car la valeur absolue de ces deux est calculée différemment

Ce n'est pas en fait une raison impérieuse contre tout héritage ici, juste le modèle class RealNumber<-> proposé class ComplexNumber.

Vous pouvez raisonnablement définir une interface Number, qui à la fois RealNumber et ComplexNumberimplémenterait.

Cela pourrait ressembler

interface Number
{
    Number Add(Number rhs);
    Number Subtract(Number rhs);
    // ... etc
}

Mais vous voudriez alors contraindre les autres Numberparamètres de ces opérations à être du même type dérivé que celui thisque vous pouvez approcher avec

interface Number<T>
{
    Number<T> Add(Number<T> rhs);
    Number<T> Subtract(Number<T> rhs);
    // ... etc
}

Ou à la place, vous utiliseriez un langage qui permettait le polymorphisme structurel, au lieu du polymorphisme de sous-type. Pour le cas spécifique des nombres, il se peut que vous n'ayez besoin que de surcharger les opérateurs arithmétiques.

complex operator + (complex lhs, complex rhs);
complex operator - (complex lhs, complex rhs);
// ... etc

Number frobnicate<Number>(List<Number> foos, Number bar); // uses arithmetic operations
Caleth
la source
0

Solution: ne pas avoir de RealNumberclasse publique

Je trouverais cela tout à fait correct si ComplexNumberj'avais une méthode d'usine statique fromDouble(double)qui retournerait un nombre complexe avec un imaginaire égal à zéro. Vous pouvez ensuite utiliser toutes les opérations que vous utiliseriez sur une RealNumberinstance sur cette ComplexNumberinstance.

Mais j'ai du mal à voir pourquoi vous voudriez / devriez avoir une RealNumberclasse héritée publique . L'héritage est généralement utilisé pour ces raisons (hors de ma tête, corrigez-moi si vous en manquez)

  • étendre le comportement. RealNumbersne peut pas faire d'opérations supplémentaires, un nombre complexe ne peut pas le faire, donc inutile de le faire.

  • implémenter un comportement abstrait avec une implémentation spécifique. Puisque ComplexNumberne doit pas être abstrait, cela ne s'applique pas non plus.

  • réutilisation du code. Si vous utilisez simplement la ComplexNumberclasse, vous réutilisez 100% du code.

  • mise en œuvre plus spécifique / efficace / précise pour une tâche spécifique. Cela pourrait être appliqué ici, RealNumberspourrait implémenter certaines fonctionnalités plus rapidement. Mais alors cette sous-classe doit être cachée derrière le statique fromDouble(double)et ne doit pas être connue à l'extérieur. De cette façon, il n'aurait pas besoin de cacher la partie imaginaire. Pour l'extérieur, il ne devrait y avoir que des nombres complexes (qui sont des nombres réels). Vous pouvez également renvoyer cette classe RealNumber privée à partir de toutes les opérations de la classe de nombres complexes qui aboutissent à un nombre réel. (Cela suppose que les classes sont immuables comme la plupart des classes numériques.)

C'est comme implémenter une sous-classe d'Integer qui s'appelle Zero et coder en dur certaines des opérations car elles sont triviales pour zéro. Vous pouvez le faire, car chaque zéro est un entier, mais ne le rendez pas public, cachez-le derrière une méthode d'usine.

findusl
la source
Je ne suis pas surpris d'obtenir un vote négatif, car je n'ai aucune source à prouver. De plus, si personne d'autre n'avait une idée, je soupçonne toujours qu'il pourrait y avoir une raison à cela. Mais dites-moi pourquoi vous pensez que c'est mal et comment vous le feriez mieux.
findusl
0

Dire qu'un nombre réel est un nombre complexe a plus de sens en mathématiques, en particulier en théorie des ensembles, qu'en informatique.
En mathématiques, nous disons:

  • Un nombre réel est un nombre complexe car l'ensemble des nombres complexes comprend l'ensemble des nombres réels.
  • Un nombre rationnel est un nombre réel car l'ensemble des nombres réels comprend l'ensemble des nombres rationnels (et l'ensemble des nombres irrationnels).
  • Un entier est un nombre rationnel car l'ensemble des nombres rationnels comprend l'ensemble des entiers.

Cependant, cela ne signifie pas que vous devez, ou même devez, utiliser l'héritage lors de la conception de votre bibliothèque pour inclure une classe RealNumber et ComplexNumber. In Effective Java, Second Edition de Joshua Bloch; Le point 16 est "Favoriser la composition plutôt que l'héritage". Pour éviter les problèmes mentionnés dans cet élément, une fois que vous avez défini votre classe RealNumber, elle peut être utilisée dans votre classe ComplexNumber:

public class ComplexNumber {
    private RealNumber realPart;
    private RealNumber imaginaryPart;

    // Implementation details are for you to write
}

Cela vous donne toute la puissance de réutiliser votre classe RealNumber pour garder votre code SEC tout en évitant les problèmes identifiés par Joshua Bloch.

Craig Noah
la source
0

Il y a deux problèmes ici. La première est qu'il est courant d'utiliser les mêmes termes pour les types de conteneurs et les types de leur contenu, en particulier avec les types primitifs comme les nombres. Le terme double, par exemple, est utilisé pour décrire à la fois une valeur à virgule flottante double précision et un conteneur dans lequel une valeur peut être stockée.

Le deuxième problème est que si les relations is-a entre les conteneurs à partir desquels différents types d'objets peuvent être lus se comportent de la même manière que les relations entre les objets eux-mêmes, celles entre les conteneurs dans lesquels différents types d'objets peuvent être placés se comportent en face de celles de leur contenu. . Chaque cage connue pour contenir une instance de Catsera une cage qui contient une instance de Animal, mais ne doit pas nécessairement être une cage qui contient une instance de SiameseCat. D'un autre côté, chaque cage pouvant contenir toutes les instances de Catsera une cage pouvant contenir toutes les instances de SiameseCat, mais ne doit pas nécessairement être une cage pouvant contenir toutes les instances de Animal. Le seul type de cage qui peut contenir toutes les instances de Catet peut être garanti ne peut jamais contenir autre chose qu'une instance deCat, est une cage de Cat. Tout autre type de cage serait soit incapable d'accepter certains exemples de Catce qu'il devrait accepter, soit serait capable d'accepter des choses qui ne sont pas des exemples de Cat.

supercat
la source