如何在javascript中实现C#访问修饰符?

时间:2014-01-14 23:52:33

标签: c# javascript inheritance encapsulation

  • 摘要

    我试图在javascript中正确地实现继承和封装,就像在基于类的语言(如c#)中一样。

    丑陋的部分是受保护的成员在私有实例中有多个副本,只能通过闭包访问,除了将这些成员刷新到私有实例之外我没有任何想法。

    如果有可能,我想在transmit的代码中删除transferFunction.extend

  • 更新 对于对引用或研究感兴趣的人,这里是源代码库:

    https://github.com/kenkins/Function.extend

  • 故事

    由于程序集可能是一个超出javascript范围的概念,我不会考虑internal修饰符,而是public,{{1} }和protected

    privatepublic修饰符并不难实现;但是对于继承,private非常棘手。然而,使用javascript并不是一个推荐的事情,我读过的大多数文章都说带有特殊字符的前缀并记录

    但似乎我坚持使用javascript来模拟基于类的语言..我偷了this idea并以我的方式实现,代码在这篇文章的后面。

    场景背后的想法是使用更高的原型提供更高的可访问性,并使用闭包访问最高的原型。

    假设我们有三个原型protectedAD,它看起来像

    BANxt.png

    因为对象不可能是另一种不属于原型链的类型的实例;我选择的方式是水平链接G级别并从声明类型的原型中复制成员。这使得嵌套类成为可能,因为在较少派生类型上声明的成员可以传播到更多派生类型;我的代码中的protected方法是这样做的。如果transmitAD拥有自己的受保护成员,则会显示如下:

    bhcsI.png

    用于访问私有实例的闭包是G。它需要一个用于识别类的参数。修饰符持有者只是类标识符,在this['']中名为y,在测试代码中名为Function.extend,它不应暴露在类声明之外。它也用作_的快捷方式。

    this['']实际上不仅是基础构造函数调用者,而且还是私有实例创建者。它为每个具有继承的构造函数创建私有实例并更新_['base'],因此应始终在构造函数中调用它。

    虽然私有实例可以访问公共成员,但不应该使用它来更改它们,因为在访问公共成员时不能保证调用this['']。但访问私有实例是; this['']会记住最近访问过的私有实例,并在发生更改时更新受保护的成员。

    我的问题是,我怎样才能摆脱受保护成员的这种清新?是否有更好的想法来实现更真实的封装?

    p.s。:我实际上不想要一个使用非标准方法/属性的解决方案..如果使用的方法/属性对于旧浏览器来说过于时尚,那么填充更好。


  • Function.extend

    recent
  • 转移

    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 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;
                }
    
                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;
        }
    };
    
  • 测试代码

    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];
                }
            }
        }
    }
    

4 个答案:

答案 0 :(得分:4)

我也有类似的想法,并决定尝试写点东西。一个香草js解决方案。还是早,但我喜欢它的结果。你也可能觉得它很有趣。

这不完全是c#,但提供了更严格的生态系统。还有一些轻量级解决方案中的其他高级js功能。

https://github.com/iamlothian/rucksack.js

这不是您的代码的解决方案,而是您的概念的解决方案。如果你的目标是让你的想法发挥作用,那么一定要继续,因为我对结果感兴趣。

如果你喜欢我只是想要一个更有条理的js环境,那么这里就是我写的一个与你的问题概念类似的野心。

第2部分:

这里的想法是使用闭包和访问限制来创建一个模式,该模式限制了代码在定义之后的使用和更改方式。在大多数情况下,已经完成了很多艰苦的工作。但是模式留给你定义。

这是一个快速模拟示例,演示如何实现public | protect | private继承。我试图决定天气,我将其中的一部分实现为内置功能,或者让用户实现他们自己的对象扩展,就像我在示例中所做的那样。

http://plnkr.co/edit/ao2hTyBV1b3nYIwr7ZS5

实现在scripts.js中。查看你的控制台,看看发生了什么。

rucksack提供的是用于创建分离的代码模块的框架。这些模块分为命名空间,可以相互依赖。这些依赖项按定义延迟解析,因此定义顺序并不重要。解决过程提供了一些其他有用的功能,如接口匹配和密封模块。

当前功能:

  • 模块化
  • 依赖注入
  • 工厂构造函数(Instances Object)
  • 服务构造函数(静态对象)
  • 延迟加载
  • 简单的错误记录(捕获模块中的所有错误并可以传递)
  • 命名空间
  • 可密封的模块和命名空间(无法从命名空间外部访问的模块)
  • 全球等待模块事件
  • 可选配置对象的接口
  • 可选的严格接口检查注入

答案 1 :(得分:3)

虽然带闭包的代码可以解决你想要的问题,但我会选择更简单的Privileged methods as Crockford called them here

使用方法很简单:

  • 在基础对象上定义特权方法(限制为1 - 只允许调用一次)。
  • 特权方法返回自身(基础对象)的受保护接口,其中包含受保护的函数(可能这些函数在基础中私有定义,然后被复制到受保护的接口对象...或者可能是受保护的接口是私有存在的。)
  • 每个对象都使用其基础对象的受保护接口扩展其受保护接口,并仍通过特权方法公开它。

你最终会得到这样的东西:

function A() {
    var protected = {
        protectedA: function() { }
    };

    this.getProtected = (function() {
        var allow = true;

        //privileged function.
        return function() {
            if (allow) {
                allow = false;
                return protected;
            }
        };
    });
}

//B - derives from (extends) A
function B() {
    var base = {}; //acquiring a base omitted - depends on your implementation.

    var protected = {
        protectedB: function() { }
    };
    //"extend" simply copies new members into protected:
    protected = $.extend(protected, base.getProtected()); 

    this.getProtected = function() { 
        /* privileged function - similar to A.getProtected */ 
    };
}

JavaScript在这个范围内的能力有限,所以protected糖还带来一些成本。

答案 2 :(得分:1)

父类和子类如何相互作用

  • 扩展子类调用super.call,这是一个构造其父实例的函数。

  • 父类通过在其构造函数中使用this.share将其受保护的成员(包括字段和函数)共享到它的扩展子类。

  • 子类也可以调用super.fetch(),它返回父类传递给this.share的字段/函数的对象


为了说明我的技术,下面的代码通过class Dog extends Animal

的简单示例向OOP演示了一些基本要素

此面向对象模型的一些核心功能

// runs in both node.js and browser
var global_namespace = ('undefined'==typeof module)? window: global;

// put a no-operation function in the value for `share` in case nothing is extending a class
var not_extendable = {share:function(){}};

// when something is extending a class...
var extendable = function(constructor) {

    // create a space for protected fields
    var protected_space = {};

    // the following is what will get passed as `this` to the parent constructor
    var sharing = {
        share: function(fields) {
            protected_space = fields;
        },
    };

    // the following is what will get passed as the first arg to the child constructor
    return {

        // enables child to call its parent's constructor
        call: function(args) {
            return constructor.apply(sharing, args);
        },

        // allows child to access protected fields shared by its parent
        fetch: function() {
            return protected_space;
        },
    };
};

Animal上课

// class definition for `Animal`
(function(namespace) {

    // construct an instance of this class
    var constructor = function(name, weight, info) {

        // private fields
        var color = (info && info.color) || 'unknown';

        // for protected fields
        var protect = {
            weight: weight,
            noise: function() {
                return 'nothing';
            },
        };

        // share the protected fields with any subclass that might be extending this
        this.share(protect);

        // public fields and methods
        return {
            speak: function() {
                console.log(name+' says '+protect.noise());
            },
            feed: function() {
                console.log(name+' is not hungry');
            },
            weigh: function() {
                console.log(name+' weighs '+protect.weight+' lbs');
            },
            toString: function() {
                return '{Animal}';
            },
        };
    };


    // handle calls to: `Animal()`
    namespace.Animal = function() {

        // called with new operator: `new Animal(...)`
        if(this !== namespace) {

            // construct simple instance of this class
            return constructor.apply(not_extendable, arguments);
        }

        // static call: `Animal(...)`, means the caller wants to extend this class
        else {

            // reference child constructor
            var child_constructor = arguments[0];

            // return a wrapped constructor function
            return function() {

                // call child constructor and allow it to call the super constructor
                return child_constructor.apply({}, [extendable(constructor), arguments]);
            };
        }
    };

})(global_namespace);

Dog上课

// class definition for `Dog`
(function(namespace) {

    // class `Dog` extends class `Animal`
    var constructor = Animal(function(super_class, args) {

        // private field
        var been_fed = false;

        // call super's constructor
        var operator = super_class.call(args);

        // inherit parent's protected members
        var parent = super_class.fetch();

        // override a protected method
        parent.noise = function() {
            return 'bark!';
        };

        // override a public method
        operator.feed = function() {
            been_fed = true;
            parent.weight += 5;
        };

        // extend a public method
        var super_weigh = operator.weigh;
        operator.weigh = function() {
            super_weigh();
            if(been_fed) console.log('\t'+args[0]+' has been eating :)');
            else console.log('\t'+args[0]+' has not been fed yet');
        };

        // override another public method
        operator.toString = function() {
            return '{Dog}';
        },

        // return the operator (interfacable instance object)
        return operator;
    });


    // handle calls to: `Dog()`
    namespace.Dog = function() {

        // called with new operator: `new Dog()`
        if(this !== namespace) {
            return constructor.apply(this, arguments);
        }

        // static call: `Dog()`
        else {

            // we do no allow extending class `Dog`
            return false;
        }
    };

})(global_namespace);

现在,我们可以这样做:

var giraffe = new Animal('Mr. Giraffe', 720);
giraffe.speak(); // "Mr. Giraffe says nothing"
giraffe.weigh(); // "Mr. Giraffe weighs 720 lbs"

var buddy = new Dog('Buddy', 50);
buddy.speak(); // "Buddy says bark!"
buddy.weigh(); // "Buddy weighs 50 lbs"
                   //   "Buddy has not been fed yet"
buddy.feed();
buddy.weigh(); // "Buddy weighs 55 lbs"
                   //   "Buddy has been eating :)"

这允许私有,受保护和公共字段/功能。受保护和公共字段/功能都可以被覆盖和扩展。

console.log(giraffe); // "{Animal}"
console.log(buddy);   // "{Dog}" 

答案 3 :(得分:1)

我一直在研究另一个有趣的javascript项目,并实现了一些可能更接近你正在寻找的东西。

Implement.js

对你的想法感兴趣。