Summary
I tried to achieve inheritance and encapsulation properly in javascript like it was in a class-based language such as c#.
The ugly part is the protected members have multiple copies in the private instances which are only accessible via closure, and I don't have an idea except refreshing those members to the private instances.
If it is possible, I want to get rid of both
transmit
andtransfer
in my code ofFunction.extend
.Update For people who are interested in citing or research, here's the source code repository:
The story
Since assemblies may be a concept which is out of range of javascript, I don't take the
internal
modifier into account, butpublic
,protected
andprivate
.public
andprivate
modifiers are not that difficult to achieve; but with inheritance,protected
is significantly tricky. Yet it's not a recommended thing to do with javascript, most of articles I've read says prefix with a special character and document it.But it seems I'm persisted to make javascript to simulate class-based languages .. I stole this idea and implemented in my way, the code is at rear of this post.
The idea behind the scene is to put higher accessibility with a higher prototype and access the highest one with a closure.
Say we have three prototypes
A
,D
andG
, it looks likeAs it is not possible that an object is an instance of a type also of another type which is not in the prototype chain; the way I chosen is to chain the
protected
level horizontally and copy the members from the prototype of the declaring type. This makes nesting class possible, because the members declared on a less-derived type can be propagated to more-derived types; thetransmit
method in my code is to do this. IfA
,D
andG
have their own protected members, it would look like:The closure for accessing the private instance, is
this['']
. It takes an argument which is for identifying a class. The modifiers holder is just the class identifier, namedy
inFunction.extend
and_
in the test code, it should not be exposed outside the class declaration. It is also used as a shortcut ofthis['']
._['base']
is in fact not only the base constructor invoker, but also the private instances creator. It creates the private instances and updatesthis['']
for each constructor with the inheritance, so it should always be called in the constructors.Although a private instance would have the access of the public members, it should not be used to alter them, since
this['']
is not guaranteed to be invoked when accessing public members. But the accessing of private instance is;recent
remembers the most recently accessed private instance, and update the protected members if there're changes.My question is, how can I get rid of this kind of refreshing the protected members? Are there better ideas to achieve the encapsulation more of the realistic?
p.s.: I actually do not want a solution which uses non-standard methods/properties .. and it would be better there're polyfills if the used methods/properties are too fashion to the old browsers.
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; } };
test code
'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());