-
Résumé
J'ai essayé de réaliser l'héritage et l'encapsulation correctement en javascript comme dans un langage basé sur les classes tel que c#.
Le problème est que les membres protégés ont plusieurs copies dans les instances privées qui ne sont accessibles que par la fermeture, et je n'ai pas d'autre idée que de rafraîchir ces membres dans les instances privées.
Si c'est possible, je veux me débarrasser des deux
transmit
ytransfer
dans mon code deFunction.extend
. -
Mise à jour Pour les personnes intéressées par la citation ou la recherche, voici le dépôt du code source :
-
L'histoire
Desde assemblages peut être un concept qui n'est pas à la portée de javascript, je ne prends pas l'option de l'utiliser.
internal
en tenant compte du modificateur, maispublic
,protected
yprivate
.public
yprivate
ne sont pas si difficiles à réaliser, mais avec l'héritage,protected
est très délicate. Pourtant, ce n'est pas une chose recommandée à faire avec javascript, la plupart des articles que j'ai lus disent avec un caractère spécial et le documenter .Mais il semble que je persiste à faire du javascript pour simuler les langages à base de classes je stole cette idée et mis en œuvre à ma façon, le code se trouve à la fin de cet article.
L'idée derrière la scène est de placer une accessibilité plus élevée avec un prototype plus élevé et d'accéder au prototype le plus élevé avec une fermeture.
Supposons que nous ayons trois prototypes
A
,D
yG
Il semble queComme il n'est pas possible qu'un objet soit une instance d'un type et d'un autre type qui n'est pas dans la chaîne de prototypes, la méthode que j'ai choisie consiste à enchaîner les éléments suivants
protected
horizontalement et copier les membres du prototype du type déclarant. Cela rend possible l'imbrication des classes, car les membres déclarés sur un type moins dérivé peuvent être propagés aux types plus dérivés ; la fonctiontransmit
dans mon code. SiA
,D
yG
ont leurs propres membres protégés, cela ressemblerait à ce qui suit :La fermeture permettant d'accéder à l'instance privée est la suivante
this['']
. Il prend un argument qui sert à identifier une classe. Le support des modificateurs est simplement l'identifiant de la classe, nomméy
enFunction.extend
y_
dans le code de test, elle ne doit pas être exposée en dehors de la déclaration de la classe. Il est également utilisé comme raccourci dethis['']
._['base']
est en fait non seulement l'invocateur du constructeur de base, mais aussi le créateur des instances privées. Il crée les instances privées et met à jourthis['']
pour chaque constructeur avec l'héritage, de sorte qu'il doit toujours être appelé dans les constructeurs.Bien qu'une instance privée ait accès aux membres publics, elle ne doit pas être utilisée pour les modifier, puisque
this['']
n'est pas garantie d'être invoquée lors de l'accès aux membres publics. En revanche, l'accès aux instances privées l'est ;recent
se souvient de l'instance privée la plus récemment consultée et met à jour les membres protégés en cas de changement.Ma question est la suivante : comment puis-je me débarrasser de ce type de rafraîchissement des membres protégés ? Existe-t-il de meilleures idées pour parvenir à une encapsulation plus réaliste ?
p.s. : En fait, je ne veux pas d'une solution qui utilise des méthodes/propétences non standard et il serait préférable qu'il y ait des polyfills si les méthodes/propétences utilisées sont trop à la mode pour les anciens navigateurs.
-
Function.extend
Function.extend=function(base, factory) { factory.call(initializeClass); updateStaticMembersOfDerivedInnerClasses(y['public'].constructor); transfer(y['protected'], y['public']); return y['public'].constructor; function y($this) { return $this[''](y); } function transfer(target, source, descriptor) { if(target!==source? 'undefined'!==typeof target? 'undefined'!==typeof source: false:false) { var keys='undefined'!==typeof descriptor? descriptor:source; for(var key in keys) { if(Object.prototype.hasOwnProperty.call(source, key)) { target[key]=source[key]; } } } } function updateStaticMembersOfDerivedInnerClasses(outer) { var member, inner; for(var key in outer) { if(Object.prototype.hasOwnProperty.call(outer, key)? (member=outer[key]) instanceof outer? outer!==(inner=member.constructor): false:false) { transfer(inner, outer); } } } function initializeInstance() { var $this=Object.create(y['private']); var derivedGet=this['']; var recent=$this; this['']=function(x) { var value=y!==x? derivedGet.call(this, x):$this; if(value!==recent) { transfer(value, recent, x['protected']); recent=value; } transfer(value, this); return value; }; base.apply(this, arguments); $this['']=this['']; } function initializeClass(derived) { y['public']=Object.create(base.prototype); y['public'].constructor=derived; if(Object.prototype.hasOwnProperty.call(base, 'transmit')) { base.transmit(y); } else { y['protected']=Object.create(y['public']); } y['private']=Object.create(y['protected']); y['base']=initializeInstance; transfer(derived, base); derived.transmit=function(x) { if(x['public'] instanceof derived) { x['protected']=Object.create(y['protected']); x['protected'].constructor=x['public'].constructor; } }; derived.prototype=y['public']; return y; } };
-
code de test
'use strict'; var BaseClass=Function.extend(Object, function () { var _=this(BaseClass); var NestedClass=Function.extend(BaseClass, function () { var _=this(NestedClass); function NestedClass(x, y, z) { _['base'].apply(this, arguments); _(this).Y=y; _(this).Z=z; } _['public'].SetX=function (x) { _(this).InternalSetX(x); }; _['public'].GetX=function () { return _(this).InternalGetX(); }; _['public'].GetY=function () { return _(this).Y; }; _['public'].SetZ=function (z) { _(this).Z=z; }; _['public'].GetZ=function () { return _(this).Z; }; _['private'].Y=0; }); function BaseClass(x) { _['base'].apply(this, arguments); _(this).X=x; } _['protected'].InternalSetX=function (x) { _(this).X=x; }; _['protected'].InternalGetX=function () { return _(this).X; }; _['private'].X=0; _['protected'].Z=0; BaseClass.Sample=new NestedClass(1, 2, 3); }); var DerivedClass=Function.extend(BaseClass, function () { var _=this(DerivedClass); function DerivedClass(x, y, z) { _['base'].apply(this, arguments); } }); var o=DerivedClass.Sample; alert(o.GetX()); alert(o.GetY()); alert(o.GetZ()); o.SetX(3); o.SetZ(1); alert(o.GetX()); alert(o.GetY()); alert(o.GetZ());
0 votes
Avez-vous envisagé un compilateur croisé comme script# à la place ? Considérez également si la prise en charge de
protected
Cela vaut vraiment la peine - "par convention" est généralement suffisant pour les équipes amicales, privé/public strict est préférable pour les utilisateurs externes ( parashift.com/c++-faq-lite/protected-data-not-evil.html )...10 votes
Il semble que TypeScript supporte les modificateurs d'accès, si cela vous intéresse, bien que ce ne soit pas protégé.
3 votes
Non, il s'agit plutôt d'écrire du code fortement typé tout en étant capable de l'utiliser avec un navigateur - C# -> JS peut être la voie la plus pratique. Note complémentaire : jusqu'à présent, votre belle et longue question semble manquer de valeur pratique, de sorte que certains pourraient la considérer comme hors sujet pour l'OS. En tout cas, un résumé de 2 ou 3 phrases au début pourrait être plus efficace que les cas TL;DR...
0 votes
@AlexeiLevenkov : Oui, j'y ai pensé
TL;DR
mais pas encore d'idée sur la manière de le faireTL;DR
il .0 votes
@KenKin : Intéressant. Bien sûr, puisqu'il inclut également les interfaces, si vous êtes du genre " a a est toujours mieux que est a ", cela conviendrait. Je ne suis pas sûr d'avoir atteint ce niveau, mais un langage qui le force dans une certaine mesure semble intrigant.
1 votes
@AlexeiLevenkov : Comment pensez-vous que ma révision de la
TL;DR
.. ?11 votes
S'agit-il d'une utilisation pratique ou d'une tentative d'amusement ? (Dans le premier cas, je suggère de ne pas essayer d'imposer les idiomes d'autres langages de programmation à JavaScript, car ils ne s'y prêtent pas).
0 votes
@minitech : Je souhaite le mettre en pour une utilisation pratique . Mais comme il y a encore des inconvénients, j'aimerais l'améliorer et faire en sorte qu'il devienne réalité.
0 votes
Vous pouvez utiliser un getter pointant vers un WeakMap pour stocker secrètement des valeurs entre les instances.
0 votes
@dandavis : MDN dit qu'il s'agit d'une technologie expérimentale mais si vous avez une idée, vous pouvez peut-être en faire une réponse ?
1 votes
C'est l'une des meilleures questions que j'ai vues sur ce site.
1 votes
@KenKin voir ma réponse, mais vous aurez besoin de temps pour la lire.
1 votes
Vous obtenez nativement ce qui est essentiellement public, privé et protégé (même statique), pourquoi voudriez-vous introduire des idiomes non JS dans le langage ?
1 votes
J'ai une solution qui utilise les notions de public, privé et protégé, ainsi que l'héritage et l'encapsulation. Il me faudra environ deux heures pour rédiger le message.
0 votes
@BlakeRegalia : Merci d'avoir accepté de poster votre réponse. Veuillez également prendre en compte l'accessibilité, la visibilité et la collecte des déchets, le code que j'ai proposé dans la question est mis en œuvre en tenant compte de ces préoccupations.
0 votes
IMO, votre question ressemble à une Problème XY question. Elle est intéressante, mais elle n'a pas de solution ultime, qui serait pratique, efficace et lisible. JavaScript n'est tout simplement pas conçu pour cela, et l'on peut se demander s'il s'agit d'une faiblesse ou d'une force du langage.
0 votes
@Noseratio : Si je comprends bien le problème xy, il s'agit de demander y pour résoudre x. Mais je pense que j'ai demandé précisément ce que j'ai essayé de résoudre. Une solution simulant ces accès ne devrait pas briser la relation des types dans l'héritage et l'encapsulation, et une solution simulant l'héritage et l'encapsulation d'un langage basé sur les types devrait faire en sorte que ces accès soient définis comme ils l'étaient dans ce langage. J'ai proposé mon code comme exemple d'implémentation pour montrer mon effort de recherche sur ce sujet.