Javascript эквивалентно C # s "защищен" - PullRequest
0 голосов
/ 15 сентября 2011

Приведенный ниже код использует Javascript для создания базового класса eventRaiser, который имеет внутренние компоненты, необходимые для того, чтобы клиенты могли подписаться на события , и подклассы для вызова этих событий.Идея состоит в том, что другие классы, такие как ThingWithEvent, будут наследоваться от eventRaiser и предоставлять метод подписки, а также запускать метод повышения внутри системы.Функция инициализации jQuery демонстрирует это.

Как это написано, ничто не мешает клиенту напрямую вызывать событие.Другими словами, добавление er.raise("Y"); к функции инициализации jQuery вызывает событие Y без проблем.

В идеале я хотел бы сделать так, чтобы внешний код, взаимодействующий с eventRaiser через некоторый класс, который наследовал от него, мог только подписываться на события, а не вызывать их.

Другими словами, я бы хотел, чтобы raise был эквивалентом C # protected - видимым только себе и классам, которые наследуют его.

Есть ли какое-то плавное закрытие ниндзя, которое я должен использовать для достижения этого, или я должен признать, что Javascript не предназначен для включения OO Encapulation, переименуйте raise в _raise, чтобы указать клиентскому коду, что _raise является закрытым и не долженвызывать и двигаться дальше?

    $(function() {
        var er = new ThingWithEvent();

        er.subscribe("X", function() { alert("Hello"); });
        er.subscribe("X", function() { alert("World"); });
        er.subscribe("Y", function() { alert("Not Called"); });

        er.doSomething("X");
    });

    function eventRaiser() {
        var events = {};
        this.subscribe = function(key, func) {
            if (!events[key])
                events[key] = { name: key, funcs: [] };
            events[key].funcs.push(func);
        };

        this.raise = function(key) {
            if (!events[key]) return;
            for (var i = 0; i < events[key].funcs.length; i++)
                events[key].funcs[i]();
        };
    }

    function ThingWithEvent() {
        eventRaiser.call(this);
        var self = this;

        this.doSomething = function() {
            alert("doing something");
            self.raise("X");
        }
    }

    function surrogate() { }
    surrogate.prototype = eventRaiser;
    ThingWithEvent.prototype = new surrogate();
    ThingWithEvent.prototype.constructor = ThingWithEvent;

Ответы [ 5 ]

2 голосов
/ 15 сентября 2011

Читать это: http://javascript.crockford.com/private.html

В javascript нет классов, поэтому вы можете создать конструктор для менеджера событий с this.subscribe(obj) в качестве метода подписки и var raise(event) в качестве частного метода для их повышения, который может вызываться только его экземплярами. .

function EventRaiser () {
     var foo = 1;  // will be private
     function raise() {  ...  }; // will be private
     var raise1 = function () {  ...  }; // will be private

     this.subscribe = function () {  ...  }; // will be privileged, has access to private vars and methods
     this.foo = 1; // public, anyone can read/write

     return this; 
}
var er = new EventRaiser (); // here we make instance of constructor
er.subscribe(); // will work
er.raise(); // will THROW error, because it is 'private'  

Локальная функция raise(event) будет видна только экземплярам eventRaiser, а не экземплярам производных конструкторов. (Но у них будет своя собственная raise функция, недоступная кому-либо еще).

1 голос
/ 07 октября 2011

Вдохновленный ответом Адама (именно то, что я искал!), Я собрал это вместе, чтобы дать защищенные методы с конструкторами / неоклассическим наследованием.

ЭТО ПОЛНОСТЬЮ НЕПРОВЕРЕНО и может быть ошибочным, но я решил выбросить его туда.

В нем используется идея "protectedStuff" из ответа Адама, чтобы разрешить двустороннюю непубличную связь между базовым классом и производным классом.

/*** Example Base Class ***/

var Example = {};

Example.BaseClass = function(shared)
{
    // Private Variables

    var protected = shared; // protected is the two-way communication mechanism between the base and derived class

    var internal = 0;

    // Protected Variables and Methods

    protected.internalStuff = function()
    {

    }

    // Public Variables and Methods

    this.public = 123;

    this.show = function(arg)
    {
        protected.onBeforeShow();

        privateMethod(arg);

        protected.onAfterShow();

        doSomeMoreStuff();
    };

    // Private Methods

    function privateMethod(arg)
    {
        internal = arg;
    }
};

/*** Example Derived Class ***/

Example.DerivedClass = function(parameter)
{
    var protected = (
    {
        onBeforeShow : function()
        {
            // ...
        },

        onAfterShow : function()
        {
            // ...
        }
    });

    // Call our base class' constructor

    Example.BaseClass.call(this, protected);    // Tells base class about our protected methods, adds its protected methods to the 'protected' variable.

    // At this point our instance has all the public methods and properties of the base class.

    // Public Methods that our derived class overrides

    var baseShow = this.show;  // Save the base class' implementation of the show method

    this.show = function(arg)
    {
        baseShow.call(this, arg);  // Call the base class' implementation

        // ..
    }

    // Public Variables and Methods Unique to This Class

    this.derivedVariable = 123;

    this.derivedMethod = function()
    {
        protected.internalStuff();
    }
};
1 голос
/ 22 сентября 2011

Я ненавижу отвечать на свой вопрос, не говоря уже о том, чтобы принять мой собственный ответ, но оказывается, что это не только возможно, но и безумно легко.Идея этого кода взята из JavaScript The Good Parts Дугласа Крокфорда .Хитрость заключается в том, чтобы полностью отказаться от конструкторов (неоклассическое наследование) и использовать функциональное наследование.

Этот код не только обеспечивает открытый, защищенный и закрытый уровни доступа, но и намного чище и проще для чтения IMO, чем наследование конструкторов и обмен прототипами и конструкторами.Единственная загвоздка в том, что этот код будет немного медленнее, поскольку каждое создание объекта требует создания каждой из функций объекта, вместо того, чтобы получать все это бесплатно с помощью прототипа конструктора.Итак, если вам нужно создать десятки тысяч объектов на вашем веб-сайте, вы можете предпочесть конструктор.Для ... всех остальных, этот код, скорее всего, для вас.

    function eventRaiser(protectedStuff) {
        protectedStuff = protectedStuff || {}; 
        var that = {};
        var events = {};  //private

        protectedStuff.raise = function(key) {
            if (!events[key]) return;
                for (var i = 0; i < events[key].funcs.length; i++)
                    events[key].funcs[i].apply(null, Array.prototype.slice.call(arguments, 1));
        };

        that.subscribe = function(key, func) {
            if (!events[key])
                events[key] = { name: key, funcs: [] };
            events[key].funcs.push(func);
        };

        return that;
    }        

    function widget() {
        var protectedStuff = {};
        var that = eventRaiser(protectedStuff);

        that.doSomething = function() { 
            alert("doing something"); 
            protectedStuff.raise("doStuffEvent");
        };

        return that;
    }

    $(function() {
        var w = widget();
        w.subscribe("doStuffEvent", function(){ alert("I've been raised"); });
        w.doSomething();

        w.protectedStuff.raise("doStuffEvent"); //error!!!!!  raise is protected
        w.raise("doStuffEvent"); //and this obviously won't work
    });
1 голос
/ 18 сентября 2011

Вы можете приблизиться к тому, что вы хотите, вернув объект с ограниченным интерфейсом:

function EventSource() {
  var events = {};
  var self = this;
  this.subscribe = function(key, func) {
    if (!events[key])
      events[key] = { name: key, funcs: [] };
    events[key].funcs.push(func);
  };

  this.raise = function(key) {
    if (!events[key]) return;
    for (var i = 0; i < events[key].funcs.length; i++)
      events[key].funcs[i]();
  };

  this.limited = function() {
    return {
      subscribe: function(k, f) { return self.subscribe(k,f);}
    };
  };
}

Затем вы можете вызвать .limited() на EventSource и получить объект с ограниченным доступом, который выМожно звонить .subscribe(), но не .raise().Если вы можете контролировать, где они создаются, например, с помощью фабрики, вы можете ограничить ущерб.

jQuery использует этот шаблон со своими Deferred объектами;Ограниченные объекты называются promises и создаются с помощью .promise().

1 голос
/ 15 сентября 2011

Рассмотрим:

function ThingWithEvent() {
    var thing = {},
        events = {};

    function raise( key ) {
        if ( !events[ key ] ) { return; }
        for ( var i = 0; i < events[ key ].funcs.length; i++ )
            events[ key ].funcs[ i ]();
    }

    thing.subscribe = function ( key, func ) {
        if ( !events[ key ] ) {
            events[ key ] = { name: key, funcs: [] };
        }
        events[ key ].funcs.push( func );
    };

    thing.doSomething = function () {
        alert( "doing something" );
        raise( "X" );
    };

    return thing;
}

Итак, каждый экземпляр ThingWithEvent получит свой собственный объект events (который является частным членом экземпляра, потому что это локальная переменная конструктора).

raise - это вложенная функция внутри конструктора, которая делает его закрытым методом экземпляра.

this.subscribe и this.doSomething являются «привилегированными» методами экземпляра. Они и только они могут получить доступ к закрытым членам и закрытым методам экземпляра.

Кстати, я определил явный thing объект, который представляет новый экземпляр. Я делаю это вместо того, чтобы просто использовать this (который представляет новый экземпляр по умолчанию), потому что он позволяет мне уникально идентифицировать новый экземпляр внутри конструктора даже во вложенных функциях - var self = this; взлом не требуется.


Обновление:
Это будет наследство:

function Thing() {
    var thing = Object.create( new EventTarget );

    thing.doSomething = function () {
        alert( "doing something" );
        this.raise( "X" );
    };

    return thing;
}

function EventTarget() {
    var events = {};

    this.raise = function ( key ) {
        if ( !events[ key ] ) { return; }
        for ( var i = 0; i < events[ key ].funcs.length; i++ )
            events[ key ].funcs[ i ]();
    }

    this.subscribe = function ( key, func ) {
        if ( !events[ key ] ) {
            events[ key ] = { name: key, funcs: [] };
        }
        events[ key ].funcs.push( func );
    };
}

Использование:

var thing = new Thing();
thing.subscribe( ... );
thing.doSomething( ... );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...