C'est très intéressant pour moi quels avantages donne une approche "classe racine globale" pour le framework. En termes simples, les raisons pour lesquelles le framework .NET a été conçu pour avoir une classe d' objet racine avec des fonctionnalités générales adaptées à toutes les classes.
Aujourd'hui, nous concevons un nouveau cadre à usage interne (le cadre sous la plate-forme SAP) et nous sommes tous divisés en deux camps - le premier qui pense que ce cadre devrait avoir une racine mondiale, et le second - qui pense le contraire.
Je suis au camp "global root". Et mes raisons pour lesquelles une telle approche donnerait une bonne flexibilité et une réduction des coûts de développement, car nous ne développerons plus de fonctionnalités générales.
Donc, je suis très intéressé de savoir quelles raisons poussent vraiment les architectes .NET à concevoir le framework de cette manière.
la source
object
dans le framework .NET en partie parce qu'il fournit des capacités de base sur tous les objets, tels queToString()
etGetHashCode()
wait()
/notify()
/notifyAll()
etclone()
.Réponses:
La cause la plus pressante pour les
Object
conteneurs (avant les génériques) qui pourraient contenir n'importe quoi au lieu d'avoir à utiliser le style C "Réécrivez-le pour tout ce dont vous avez besoin". Bien sûr, sans doute, l'idée que tout devrait hériter d'une classe spécifique, puis abuser de ce fait pour perdre complètement chaque grain de sécurité de type est si terrible qu'elle aurait dû être un voyant géant sur l'expédition de la langue sans génériques, et signifie également quiObject
est complètement redondant pour le nouveau code.la source
Object
qui ne peut pas être écritvoid *
en C ++?void*
c'était mieux? Ce n'est pas le cas. Si quoi que ce soit. c'est encore pire .void*
est pire en C avec toutes les conversions de pointeurs implicites, mais C ++ est plus strict à cet égard. Au moins, vous devez lancer explicitement.Je me souviens qu'Eric Lippert avait dit une fois que l'héritage de la
System.Object
classe offrait «la meilleure valeur pour le client». [modifier: oui, il l'a dit ici ]:Le fait que tout dérive de la
System.Object
classe offre une fiabilité et une utilité que je respecte beaucoup. Je sais que tous les objets auront un type (GetType
), qu'ils seront en ligne avec les méthodes de finalisation du CLR (Finalize
), et que je peux les utiliserGetHashCode
pour traiter des collections.la source
GetType
, vous pouvez simplement l'étendretypeof
pour remplacer sa fonctionnalité. Quant àFinalize
, alors en fait, de nombreux types ne sont pas du tout finalisables et ne possèdent pas de méthode Finalize significative. En outre, de nombreux types ne sont ni lavables ni convertibles en chaînes, en particulier les types internes qui ne sont jamais destinés à une telle chose et ne sont jamais distribués à un usage public. Tous ces types ont leurs interfaces inutilement polluées.GetType
,Finalize
ouGetHashCode
pour tousSystem.Object
. J'ai simplement déclaré qu'ils étaient là si vous les vouliez. Quant à "leurs interfaces inutilement polluées", je me réfère à ma citation originale d'elippert: "meilleur rapport qualité / prix pour le client". De toute évidence, l'équipe .NET était disposée à faire face aux compromis.Je pense que les concepteurs Java et C # ont ajouté un objet racine car il était essentiellement gratuit pour eux, comme dans le déjeuner gratuit .
Les coûts d'ajout d'un objet racine sont très égaux aux coûts d'ajout de votre première fonction virtuelle. Étant donné que CLR et JVM sont des environnements de récupération de place avec des finaliseurs d'objets, vous devez avoir au moins un virtuel pour votre
java.lang.Object.finalize
ou pour votreSystem.Object.Finalize
méthode. Par conséquent, les coûts d'ajout de votre objet racine sont déjà prépayés, vous pouvez obtenir tous les avantages sans payer pour cela. C'est le meilleur des deux mondes: les utilisateurs qui ont besoin d'une classe racine commune obtiendraient ce qu'ils veulent, et les utilisateurs qui s'en moquent seraient en mesure de programmer comme si elle n'était pas là.la source
Il me semble que vous avez trois questions ici: la première est pourquoi une racine commune a été introduite dans .NET, la seconde est quels sont les avantages et les inconvénients de cela, et la troisième est de savoir si c'est une bonne idée pour un élément de framework d'avoir un global racine.
Pourquoi .NET a-t-il une racine commune?
Au sens le plus technique, avoir une racine commune est essentiel pour la réflexion et pour les conteneurs pré-génériques.
De plus, à ma connaissance, avoir une racine commune avec des méthodes de base telles que
equals()
et ahashCode()
été reçu très positivement en Java, et C # a été influencé par (entre autres) Java, alors ils voulaient également avoir cette fonctionnalité.Quels sont les avantages et les inconvénients d'avoir une racine commune dans une langue?
L'avantage d'avoir une racine commune:
equals()
ethashCode()
. Cela signifie, par exemple, que chaque objet en Java ou C # peut être utilisé dans une carte de hachage - comparez cette situation avec C ++.printf
méthode similaire.object
. Pas très courant, peut-être, mais sans racine commune, ce ne serait pas possible.L'inconvénient d'avoir une racine commune:
Devriez-vous aller avec une racine commune dans votre projet-cadre?
Très subjectif, bien sûr, mais quand je regarde la liste ci-dessus, je répondrais certainement oui . Plus précisément, la dernière puce de la liste des professionnels - la flexibilité de modifier ultérieurement le comportement de tous les objets en changeant la racine - devient très utile dans une plate-forme, dans laquelle les modifications apportées à la racine sont plus susceptibles d'être acceptées par les clients que les modifications apportées à un ensemble Langue.
En outre - bien que cela soit encore plus subjectif - je trouve le concept d'une racine commune très élégant et attrayant. Cela facilite également l'utilisation de certains outils, par exemple, il est maintenant facile de demander à un outil d'afficher tous les descendants de cette racine commune et de recevoir un bon aperçu rapide des types de framework.
Bien sûr, les types doivent être au moins légèrement liés pour cela, et en particulier, je ne sacrifierais jamais la règle "is-a" juste pour avoir une racine commune.
la source
Mathématiquement, c'est un peu plus élégant d'avoir un système de type qui contient top , et permet de définir un langage un peu plus complètement.
Si vous créez un framework, alors les choses seront nécessairement consommées par une base de code existante. Vous ne pouvez pas avoir un supertype universel car tous les autres types de ce qui consomme le framework existent.
À ce stade, la décision d'avoir une classe de base commune dépend de ce que vous faites. Il est très rare que beaucoup de choses aient un comportement commun, et qu'il est utile de référencer ces choses via le seul comportement commun.
Mais ça arrive. Si c'est le cas, alors allez de l'avant en faisant abstraction de ce comportement commun.
la source
Ils ont poussé pour un objet racine car ils voulaient que toutes les classes du framework prennent en charge certaines choses (obtenir un code de hachage, convertir en chaîne, vérifier l'égalité, etc.). C # et Java ont trouvé utile de mettre toutes ces fonctionnalités communes d'un objet dans une classe racine.
Gardez à l'esprit qu'ils ne violent aucun principe de la POO ou quoi que ce soit. Tout élément de la classe Object a un sens dans n'importe quelle sous-classe (lire: n'importe quelle classe) du système. Si vous choisissez d'adopter cette conception, assurez-vous de suivre ce modèle. Autrement dit, n'incluez pas dans la racine des éléments qui n'appartiennent pas à chaque classe unique de votre système. Si vous suivez attentivement cette règle, je ne vois aucune raison pour laquelle vous ne devriez pas avoir une classe racine qui contient du code commun utile pour l'ensemble du système.
la source
Quelques raisons, des fonctionnalités communes que toutes les classes enfants peuvent partager. Et cela a également donné aux rédacteurs de framework la possibilité d'écrire beaucoup d'autres fonctionnalités dans le framework que tout le monde pouvait utiliser. Un exemple serait la mise en cache et les sessions ASP.NET. Presque tout peut y être stocké, car ils ont écrit les méthodes add pour accepter des objets.
Une classe racine peut être très séduisante, mais elle est très, très facile à mal utiliser. Serait-il possible d'avoir une interface root à la place? Et juste une ou deux petites méthodes? Ou cela ajouterait-il beaucoup plus de code que ce qui est requis pour votre framework?
Je demande je suis curieux de savoir quelles fonctionnalités vous devez exposer à toutes les classes possibles dans le cadre que vous écrivez. Cela sera-t-il vraiment utilisé par tous les objets? Ou par la plupart d'entre eux? Et une fois que vous aurez créé la classe racine, comment empêcherez-vous les gens d'y ajouter des fonctionnalités aléatoires? Ou des variables aléatoires qu'ils veulent être "globales"?
Il y a plusieurs années, il y avait une très grande application où j'ai créé une classe racine. Après quelques mois de développement, il était peuplé de code qui n'avait rien à faire là-dedans. Nous convertissions une ancienne application ASP et la classe racine était le remplacement de l'ancien fichier global.inc que nous avions utilisé dans le passé. J'ai dû apprendre cette leçon à la dure.
la source
Tous les objets de tas autonomes héritent de
Object
; cela a du sens car tous les objets de tas autonomes doivent avoir certains aspects communs, comme un moyen d'identifier leur type. Sinon, si le garbage collector avait une référence à un objet tas de type inconnu, il n'aurait aucun moyen de savoir quels bits dans le blob de mémoire associé à cet objet devraient être considérés comme des références à d'autres objets tas.De plus, dans le système de types, il est commode d'utiliser le même mécanisme pour définir les membres des structures et les membres des classes. Le comportement des emplacements de stockage de type valeur (variables, paramètres, champs, emplacements de tableau, etc.) est très différent de celui des emplacements de stockage de type classe, mais ces différences de comportement sont obtenues dans les compilateurs de code source et le moteur d'exécution (y compris le compilateur JIT) plutôt que d'être exprimé dans le système de type.
Une conséquence de cela est que la définition d'un type de valeur définit effectivement deux types - un type d'emplacement de stockage et un type d'objet de tas. Les premiers peuvent être implicitement convertis en seconds, et les seconds peuvent être convertis en premiers via typecast. Les deux types de conversion fonctionnent en copiant tous les champs publics et privés d'une instance du type en question à une autre. De plus, il est possible d'utiliser des contraintes génériques pour appeler directement des membres d'interface sur un emplacement de stockage de type valeur, sans en faire une copie au préalable.
Tout cela est important car les références aux objets de tas de type valeur se comportent comme des références de classe et non comme des types de valeur. Considérez, par exemple, le code suivant:
Si la
testEnumerator()
méthode est transmise, un emplacement de stockage de type valeurit
recevra une instance dont les champs public et privé sont copiés à partir de la valeur transmise. La variable localeit2
contiendra une autre instance dont tous les champs sont copiésit
. L' appelMoveNext
àit2
n'affectera pasit
.Si le code ci-dessus reçoit un emplacement de stockage de type classe, la valeur transmise,,
it
etit2
, fera tous référence au même objet, et donc appeler l'MoveNext()
un d'eux l'appellera effectivement sur chacun d'eux.Notez que la conversion
List<String>.Enumerator
pourIEnumerator<String>
le transformer efficacement d'un type de valeur en un type de classe. Le type de l'objet tas estList<String>.Enumerator
mais son comportement sera très différent du type de valeur du même nom.la source
List<T>.Enumerator
qui est stocké dans unList<T>.Enumerator
est significativement différent du comportement de celui qui est stocké dans unIEnumerator<T>
, en ce que le stockage d'une valeur de l'ancien type dans un emplacement de stockage de l'un ou l'autre type fera une copie de l'état de l'énumérateur, tout en stockant une valeur de ce dernier type dans un emplacement de stockage qui est également de ce dernier type ne fera pas de copie.Object
n'a rien à voir avec cela.System.Int32
est également une instance deSystem.Object
, mais un emplacement de stockage de typeSystem.Int32
ne contient ni uneSystem.Object
instance ni une référence à une.Cette conception remonte vraiment à Smalltalk, que je considérerais en grande partie comme une tentative de poursuivre l'orientation de l'objet au détriment de presque toutes les autres préoccupations. En tant que tel, il a tendance (à mon avis) à utiliser l'orientation objet, même lorsque d'autres techniques sont probablement (ou même certainement) supérieures.
Le fait d'avoir une seule hiérarchie avec
Object
(ou quelque chose de similaire) à la racine facilite (par exemple) la création de vos classes de collection en tant que collections deObject
, il est donc trivial qu'une collection contienne tout type d'objet.En contrepartie de cet avantage plutôt mineur, vous obtenez cependant toute une série d'inconvénients. D'abord, du point de vue du design, vous vous retrouvez avec des idées vraiment folles. Au moins selon la vision Java de l'univers, qu'est-ce que l'athéisme et une forêt ont en commun? Qu'ils ont tous les deux des codes de hachage! Une carte est-elle une collection? Selon Java, non, ce n'est pas le cas!
Dans les années 70, lorsque Smalltalk était conçu, ce genre de non-sens a été accepté, principalement parce que personne n'avait conçu une alternative raisonnable. Smalltalk a été finalisé en 1980, et en 1983, Ada (qui comprend les génériques) a été conçu. Bien qu'Ada n'ait jamais atteint le genre de popularité que certains prédisaient, ses génériques étaient suffisants pour supporter des collections d'objets de types arbitraires - sans la folie inhérente aux hiérarchies monolithiques.
Lorsque Java (et dans une moindre mesure, .NET) a été conçu, la hiérarchie des classes monolithiques était probablement considérée comme un choix «sûr» - avec des problèmes, mais surtout des problèmes connus . La programmation générique, en revanche, était celle que presque tout le monde (même alors) a réalisé était au moins théoriquement une bien meilleure approche du problème, mais que beaucoup de développeurs à orientation commerciale considéraient plutôt mal explorée et / ou risquée (c'est-à-dire dans le monde commercial , Ada a été largement rejeté comme un échec).
Mais soyons clairs: la hiérarchie monolithique était une erreur. Les raisons de cette erreur étaient au moins compréhensibles, mais c'était quand même une erreur. C'est une mauvaise conception, et ses problèmes de conception imprègnent presque tout le code qui l'utilise.
Pour un nouveau design aujourd'hui, cependant, il n'y a pas de question raisonnable: l'utilisation d'une hiérarchie monolithique est une erreur claire et une mauvaise idée.
la source
Ce que vous voulez faire est en fait intéressant, il est difficile de prouver si c'est mal ou bien jusqu'à ce que vous ayez terminé.
Voici quelques pistes de réflexion:
Ce sont les deux seules choses qui peuvent bénéficier d'une racine partagée. Dans un langage, il est utilisé pour définir quelques méthodes très courantes et vous permet de passer des objets qui n'ont pas encore été définis, mais ceux-ci ne devraient pas s'appliquer à vous.
De plus, par expérience personnelle:
J'ai utilisé une boîte à outils écrite par un développeur qui avait une formation de Smalltalk. Il a fait en sorte que toutes ses classes "Data" étendent une seule classe et toutes les méthodes ont pris cette classe - jusqu'ici tout va bien. Le problème est que les différentes classes de données n'étaient pas toujours interchangeables, alors maintenant mon éditeur et mon compilateur ne pouvaient plus me donner d'aide sur ce qu'il fallait passer dans une situation donnée et j'ai dû me référer à ses documents qui ne l'ont pas toujours aidé. .
C'était la bibliothèque la plus difficile à utiliser que j'ai jamais eu à gérer.
la source
Parce que c'est la voie de la POO. Il est important d'avoir un type (objet) qui puisse faire référence à n'importe quoi. Un argument de type objet peut accepter n'importe quoi. Il y a aussi l'héritage, vous pouvez être sûr que ToString (), GetHashCode () etc. sont disponibles sur tout et n'importe quoi.
Même Oracle a réalisé l'importance d'avoir un tel type de base et prévoit de supprimer les primitives en Java vers 2017
la source
ToString()
et que vousGetType()
serez sur chaque objet. Il convient probablement de souligner que vous pouvez utiliser une variable de typeobject
pour stocker n'importe quel objet .NET.