4
\$\begingroup\$

Since the original post, I have explored further regarding this pattern. In an effort to fix one self-percieved flaw (the lack of ability of prototype methods to access private object members), I have revised this Class pattern. As no one has responded regarding the pattern, I am offering a bounty.

In general, only people with a high level of expertise in JavaScript will probably be able to review this to my requirements. To be clear, here are my requirements:

  • I am not looking for a review that utilizes convention as its primary argument.
  • Code style should only be a factor, if and only if it can keep the pattern intact:
    • All scopes must maintain their accessibility to the other scopes.
  • Clear and Concise review regarding potential security issues.
  • Verifiable performance statistics (if this is one of your review's arguments).
  • Supplemental code and reasoning (for instance, where one would use Object.freeze() or places to consider validation).
  • Other patterns to consider, if they can meet the scoping guidelines.

Please, keep in mind that this is an abstract pattern. The pattern works, though the code utilizes pseudocode. Code Edit Class private member privateAccess was changed to this.privateAccess to allow for proper inheritance. There will be another edit to account for exploits of this.

[var | Namespace.]ClassName = (function(Class) {
    /* Any static(class-wide) privates go here */        
    var privateMethod = function() {
        return 'I\'m doing something privately';
    };


    /* The class prototype */
    Class.prototype = {};  // or new BaseClass();
    Class.prototype.propertyName = function(value) {
        var returnValue;
        // Unlock private Access
        this.privateAccess = true;

        // Forward the property access
        returnValue = this.privateAccessFunction('propertyName', value);

        // Lock it back up again!
        delete this.privateAccess;
        return returnValue;
    };
    // Lock the function, so it can't be modified.
    Object.freeze(Class.prototype.propertyName);
    // Optional: Lock the prototype


    /* The class constructor */
    Class.instance = function(config) {
        // Fail if called without using new!
        if (!this instanceof Class)
            return undefined;

        // So derivative classes don't share privates
        if (config) {
            var myPrivateMembers = {
                propertyName: 'value'
            };

            Object.defineProperty(
                this, 
                'privateAccessFunction',
                {
                    //This is the actual Getter/Setter
                    // P.S. I like chainable properties
                    value: function(name, value) {
                        // Protects our function from call(obj)
                        if (!this.privateAccess || !this instanceof Class)
                            return undefined;

                        if (name !== undefined && typeof name === 'string') {
                            if (value !== undefined) {
                                myPrivateMembers[name] = value;
                                return this;
                            }
                            return myPrivateMembers[name];
                        }
                        return undefined;
                    },
                    writable: false,
                    configurable: false,
                    enumerable: false
                }
            );
        }
    };

    // So that we get the Class back
    return Class;
}([Namespace.]ClassName = [Namespace.]ClassName || function(config){
    if (this instanceof [Namespace.]ClassName)
        return [Namespace.]ClassName.instance.call(this, config);
}));

After much research, I've determined that I really like this pattern for the following reasons:

Debugging (in Chrome)

  1. ClassNames are clear without having to open the object (even for private Classes).
  2. They remain this way even after minifying with several libraries.

Object Advantages

  1. Object has access to Object Privates & Static/Class Privates
  2. Object controls which private data to expose to Prototype Methods.
  3. Private data may be locked/unlocked on a per method basis.

Prototype Advantages

  1. Prototype Methods have safe access to its own private members.
  2. Prototype Methods are chainable.
  3. Prototype Methods are only defined once. (if modifying the prototype in a constructor, it gets modified every time the constructor runs)
  4. Prototype Methods have access to static private members.

Known Disadvantages

  1. Classes are a little heavier.
  2. PrivateAccess is not thread-safe(the privateAccess variable is static). This should be fine for synchronous code, however.
  3. Derivative classes must conform to a similar pattern (privateAccess and accessPrivateMember())

Final Note

In all of my years, this class pattern is the closest I've ever gotten to classical OOP, without losing any benefit that ECMAScript/JavaScript has. Additionally, it doesn't seem to require additional extension functions. Further, it doesn't inhibit the use of any other type of inheritance that JS provides. While this wasn't the goal, at the time, it is certainly a consideration for me.

\$\endgroup\$
  • \$\begingroup\$ Where is value and value2 set ? Also, this code does not work you are returning ClassName while you are setting it causing the obvious ReferenceError: ClassName is not defined Please post working code. \$\endgroup\$ – konijn Nov 4 '13 at 21:48
  • \$\begingroup\$ For the purposes of this question, exactly which characteristics do you consider to be part of this "pattern"? The use of privateAccess to guard against usage by outsiders? Freezing the property accessor? Anything else? \$\endgroup\$ – 200_success Nov 14 '13 at 20:28
  • \$\begingroup\$ It is mentioned at the top. The Scope access properly mimicking private member access by Object and Prototype properties. Additionally, unmentioned, it correctly utilizes the new operator, and this should remain intact. If another way can be found to allow Object methods and Prototype methods access to the same Object level private members, that is acceptable. \$\endgroup\$ – Fuzzical Logic Nov 14 '13 at 20:31
  • \$\begingroup\$ It must be noted that the chainability of said property was a flourish and not a required characteristic. I would also have to maintain the ability of Object and Prototype members to access Class level private members. \$\endgroup\$ – Fuzzical Logic Nov 14 '13 at 20:34
  • \$\begingroup\$ @FuzzicalLogic What's preventing arbitrary client code from setting privateAccess to true and fetching the private property? If you're relying on client code being polite, what's the advantage over just documenting a property as being private? \$\endgroup\$ – sqykly Nov 18 '13 at 15:51
3
\$\begingroup\$

Regarding Code

Some changes can actually substantially improve the performance and usability of the code while keeping the requested features intact.

  1. Get rid of the ClassName = ClassName || function(){}. This isn't actually necessary and limits the pattern. Instead, to keep your desired features, just stick the constructor.

  2. Removing the this instanceof ClassName check from the passed function should be a serious consideration. This is already performed in the static instance method. This will dramatically improve performance and allow for additional capability. The only reason to keep that particular instanceof check is if you would like to Class to have specifically different behavior when a new object is not requested.

  3. I suggest renaming instance() to create(). This is a more descriptive and semantic name. Additionally, if modified to allow for static creation (without new), it will fit both purposes.

  4. If you are providing private access to a Class's prototype, the capability should be custom built for each class and not inherited. I would move the privateAccess variable back to the static level.

  5. If you want a more generalized pattern, you might weigh the option of passing arguments (though an es5-shim might be required) or individually passing the argument names. The 1st option is more general and accessible. The 2nd performs faster.

  6. To generalize even better with more added benefit, add Class.prototype.class = Class; change the ClassName.instance.call to this.class.create.apply(this, [argument method here]).

  7. Get rid of the Object.defineProperty. Its not needed to limit the access to the private members. The privateAccess does that. Even if the method is somehow changed, it will no longer have access to the private members it is exposing.

Result after Suggestions

[var ClassName = |Namespace.ClassName = |property: ]
(function(Class) {
    var privateAccess = false;

    /* Any static(class-wide) privates go here */        
    var privateMethod = function() {
        return 'I\'m doing something privately';
    };


    /* The class prototype */
    Class.prototype = {};  // or new BaseClass();
    Class.prototype.propertyName = function(value) {
        var returnValue;
        privateAccess = true;
        returnValue = this.requestPrivate('propertyName', value);
        privateAccess = false;
        return returnValue;
    };

    /* The class constructor */
    Class.create = function(config) {
        var myPrivateMembers = {
            propertyName: 'value'
        };

        function requestPrivate(name, value) {
            if (privateAccess && name !== undefined && typeof name === 'string') {
                if (value !== undefined) {
                    myPrivateMembers[name] = value;
                    return this;
                }
                return myPrivateMembers[name];
            }
            return undefined;
        }

        if (this instanceof Class) {
          // Do your object setup here
        }
        else {
            return new Class(arguments);
        }
    };

    return Class;
}(function(){return this.class.create.apply(this, arguments);}));

After these modifications, the pattern may now be used with a var, Namespace/Module or even as part of an object expression with no further changes except implementation choices. Instantiation occurs as before:

new ClassName(args);

with an additional option

ClassName.create(args);

Regarding Performance

I have built a series of performance tests comparing this "Modular Class Pattern" with both the Prototypal and typical Constructor class structures. I considered comparing with the "Closure Classes" from OO.js but they are so similar that to do so would merely be a curiosity. The performance tests are quite extensive. You may look at the jsPerf here.

The statistics are quite interesting, especially across browsers. The most significant point of interest here is that you can hoist any method and in most cases get member access that rivals Prototype methods and Constructor methods. Static methods can show substantially more performance. Because of this pattern, it must be noted that the only thing that differentiates a static method from a hoisted object method is whether or not the method uses this.

Object creation is also interesting because it is significantly higher performing than Constructor, but only 1/2 as good (read the test notes) as defining via pure-prototype. Overall, you'll be able to see that the pattern generally performs well across implementation choices. One thing of note: things get slower when calling prototype methods internally from the constructor or constructor members from the prototype. (Both access hoisted methods with similar statistics) Performance drops even more when accessing private object members from outside of the constructor.

In other words, while the other features you require may provide substantial benefits, the private access to prototype methods should be used judiciously, if at all. (Interestingly, the requestPrivate function may also be used with hoisted object methods, as well)

Usability

It is extremely surprising how flexible this pattern is and how many ways even simple member access can be achieved. The tests regarding both inheritance and nested (public and private) classes should also be taken note of, though they are not currently public.

All in all this seems like a pretty useful utilitarian pattern that allows min/max-ing according to the specific needs of the class more than a limitation of the pattern itself. As the additional tests become public, it may emphasize this even more.

Security

I am sorry to say, I have no further insights on the security of the private member access.

\$\endgroup\$
2
+50
\$\begingroup\$

It would be great if your code would actually instantiate a Class / object and maybe execute a function or get a value.

You require new according to line 29, you should probably not return undefined, but either throw an exception or create the object yourself.

All in all, your code seems overkill for little benefit and has drawbacks like JSON.stringify() not being able to serialize your class.

I would encourage you to read Learning JS Patterns

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.