Eine andere Möglichkeit, OOP in JS zu organisieren

Einleitung


Wie kürzlich in einer Veröffentlichung in Honest Private Properties in einem Prototyp angegeben , gibt es zwei Camps für JavaScript-Entwickler:
  • diejenigen, die bereit sind, Präfixe als Bezeichnung für das Ausblenden von Eigenschaften / Methoden zu tolerieren;
  • diejenigen, die nicht bereit sind, eine Pseudokapselung zu ertragen.


Ich beziehe mich auf das zweite Lager und löse das Problem, indem ich die gesamte Klasse in ihrem Konstruktor deklariere, wodurch private / public in jeder Kombination mit static verwendet werden kann .

Zum Beispiel Computer.js :

(function (module) {
    'use strict';
    var privateStaticVariable = 1;
    function Computer() {
        var privateVariable = 5;
        this.publicVariable = 8;
        Computer.publicStaticVariable = 1;
        this.getAnswer = function () {
            return 
                privateStaticVariable +
                Computer.publicStaticVariable +
                this.publicVariable *
                privateVariable
            ;
        };
    }
    module.exports = Computer;
}(module));


Das problem


Alles wäre in Ordnung, aber OOP ist nicht auf offen / geschlossen beschränkt, daher wurden viele Ziele gebrochen und etliche Möglichkeiten zur Lösung des Problems der geschützten Eigenschaften von Klassen entwickelt, von denen die meisten im Grunde Konventionen verwenden (wieder zurück zu Pseudo-Einkapselung und Präfixen), viel mehr und führt seine Syntax ein. Während der Suche haben wir es sogar geschafft, eine Bibliothek mit eval () im Kern zu sehen, aber wir wissen:
Eval ist böse!
Seltsamerweise war Letzteres (seiner subjektiven Meinung nach) das Beste von allen, die anhand der folgenden Kriterien untersucht wurden:
  • keine Vereinbarungen, echte Sicherheit;
  • Keine spezifische Syntax, nur native JS.

Forschung


Nach einer kurzen Untersuchung des Quellcodes wurde festgestellt, dass "Sicherheit" durch Verschieben des privaten Codes in die untergeordnete Klasse durch reguläre Ausdrücke, .toSource () , eval () und magic , bereitgestellt wurde . Natürlich konnte dieses Wunder der Technik nicht schnell funktionieren, und private Spenden für geschützte Menschen sind nicht sehr interessant.

Lösung


Erste Stufe

Es wurde beschlossen, zu überlegen, was alle Klassen der Hierarchie haben könnten und gleichzeitig für Benutzer dieser Hierarchie völlig unzugänglich zu sein. Am dritten Tag traf ihn ein Gedanke, kurz bevor er das ganze Unternehmen begrub und sich mit dem zufrieden gab, was ist: Der Konstruktorparameter !

Zweite Stufe

Damit war der erste Teil des Denkprozesses abgeschlossen. Der nächste Schritt bestand darin, herauszufinden, wie dieses zusätzliche Argument den Benutzern des Codes verborgen werden kann. Und bei diesem Problem helfen uns Sprache und Kreativität:
  1. Wir speichern den Klassenkonstruktor.
  2. Wir schreiben den Klassenkonstruktor um.
  3. Erstellen Sie eine private Variable (Objekt).
  4. Wir verwenden Function.prototype.bind.apply () für den gespeicherten Konstruktor mit dem Parameter [null, savedPrivateObject] .

Aber so viele Aktionen manuell auszuführen ist eine lange Zeit, und ein guter Entwickler ist ein fauler Entwickler ( ObjectOriented.js ):

(function () {
    /**
    * Inject protected-data object to class
    * @private
    * @param Class {Function} Class
    * @param protectedData {Object} Protected-data object
    * @return {Function} Result class
    */
    function injectProtected(Class, protectedData) {
        return (function (Native) {
            function Overridden() {
                var args = Array.prototype.map.call(arguments, function (value) { return [value]; });
                args.unshift(protectedData);
                args.unshift(null);
                return (new (Function.prototype.bind.apply(Native, args))());
            }
            Overridden.prototype = new Native({});
            return Overridden;
        }(Class));
    } 
}());

"Was ist mit Vererbung?" Oder die vierte Stufe

Um dieses Problem zu lösen, benötigen wir zunächst die Funktionalität, um festzustellen, ob unser geschützter Abschnitt implementiert ist. Dies wird durch Hinzufügen einer statischen Methode für die Klasse gelöst, z. B. isProtectedInjected () . Nachdem wir nun Informationen über das Vorhandensein einer Implementierung erhalten haben, können wir überlegen, wie dasselbe Objekt in der Klasse und all ihren Nachkommen implementiert werden soll. Dazu müssen wir zumindest die ursprüngliche Klasse abrufen können, bevor wir ein geschütztes Objekt einbetten. Daher fügen wir eine statische Methode hinzu, die die ursprüngliche Klasse zurückgibt. Die Vererbung selbst ist für JavaScript Standard, mit der Ausnahme, dass eine zusätzliche geschützte Datenvariable erstellt und in Klassen eingefügt wird, die implementiert werden müssen.

Die letzte Etappe oder "Bequemlichkeit hinzufügen"

Es ist Zeit, den Code, der einige der OOP-Funktionen implementiert, in einen objektorientierten Stil zu bringen. Fügen Sie die integrierten globalen Klasse Funktion Methoden injectProtected () , extend () und Stub - Methoden isProtectedInjected , getNative () . Dies wird dazu beitragen, Ihr Leben zu vereinfachen, weil Danach verfügt jede Klasse über diese Funktionen.

Ergebnis


(function (Function) {
    'use strict';
    /**
     * Check if protected-data was injected
     * @returns {boolean}
     */
    Function.prototype.isProtectedInjected = function () {
        return false;
    };
    /**
     * Inject protected-data object to class
     * @private
     * @param Class {Function} Class
     * @param protectedData {Object} Protected-data object
     * @return {Function} Result class
     */
    function injectProtected(Class, protectedData) {
        return (function (Native) {
            function Overridden() {
                var args = Array.prototype.map.call(arguments, function (value) { return [value]; });
                args.unshift(protectedData);
                args.unshift(null);
                return (new (Function.prototype.bind.apply(Native, args))());
            }
            Overridden.prototype = new Native({});
            Overridden.getNative = function () {
                return Native;
            };
            Overridden.isProtectedInjected = function () {
                return true;
            };
            return Overridden;
        }(Class));
    }
    /**
     * Get native class without injection of protected
     * @returns {Function} Class
     */
    Function.prototype.getNative = function () {
        return this;
    };
    /**
     * Extend from @a ParentClass
     * @param {Function} ParentClass
     * @return {Function} Result class
     */
    Function.prototype.extend = function (ParentClass) {
        var protectedData = {},
            parent,
            me = this.getNative();
        if (ParentClass.isProtectedInjected()) {
            ParentClass = injectProtected(ParentClass.getNative(), protectedData);
        }
        parent = new ParentClass();
        me.prototype = parent;
        me.prototype.constructor = me;
        protectedData.parent = parent;
        if (me.isProtectedInjected()) {
            me = injectProtected(me, protectedData);
        }
        me.prototype = parent;
        me.prototype.constructor = me;
        return me;
    };
    /**
     * Injects protected-data object to class
     * @example
     *  function SomeClass(protectedData/\* , ... *\/) {
     *      protectedData.protectedMethod = function () {};
     *      protectedData.protectedVariable = 'Access only from children and self';
     *      /\* ...Realization... *\/
     *  }.injectProtected()
     * @returns {Function}
     */
    Function.prototype.injectProtected = function () {
        return injectProtected(this, {});
    };
}(Function));

Anwendungsbeispiel


Computer.js :
(function (module) {
    'use strict';
    var privateStaticVariable = 1;
    function Computer(protectedData) {
        var privateVariable = 5;
        this.publicVariable = 8;
        protectedData.badModifier = 0.1;
        Computer.publicStaticVariable = 1;
        this.getAnswer = function () {
            return (
                privateStaticVariable +
                Computer.publicStaticVariable +
                this.publicVariable *
                privateVariable
            ) * protectedData.badModifier;
        };
    }
    module.exports = Computer.injectProtected(); // <- That's it!
}(module));

FixedComputer, js :
(function (module) {
    'use strict';
    var Computer = require('Computer');
    function FixedComputer(protectedData) {
        Computer.call(this); // Super analogue
        protectedData.badModifier = 1;
    }
    module.exports = FixedComputer.injectProtected().extend(Computer); // <- That's it!
}(module));

Referenzen


Bibliotheken:

Jetzt auch beliebt: