zoukankan      html  css  js  c++  java
  • Cesium原理篇:Property

           之前主要是Entity的一个大概流程,本文主要介绍Cesium的属性,比如defineProperties,Property(ConstantProperty,CallbackProperty,ConstantPositionProperty)以及createPropertyDescriptor的相关内容,研究一下Cesium对Object的属性设计和使用方式。

           我们以Entity为例,看看它是如何封装自己的属性:

    function Entity(options) {
        var id = options.id;
        if (!defined(id)) {
            id = createGuid();
        }
        this._id = id;
        this._name = options.name;
        this._description = undefined;
        this._position = undefined;
        this._rectangle = undefined;
    }
    
    // Key 1:defineProperties
    defineProperties(Entity.prototype, {
        id : {
            get : function() {
                return this._id;
            }
        },
        // Key 2:createRawPropertyDescriptor
        name : createRawPropertyDescriptor('name'),
        // Key 3:createPropertyDescriptor
        description : createPropertyDescriptor('description'),
        // Key 4:createPositionPropertyDescriptor
        position : createPositionPropertyDescriptor('position'),
        // Key 5:createPropertyTypeDescriptor
        rectangle : createPropertyTypeDescriptor('rectangle', RectangleGraphics)
    });

           如上,我截取了五个属性:id,name,description,position,rectangle。每个属性的创建方式可以说大同小异。下面看看用户是如何使用Entity的这些属性:

    var obj = new Cesium.Entity();
    var id = obj.id;
    obj.id = 123; // 不起作用
    obj.name = "Hello World"
    obj.description = "empty entity";

           如上说明亮点,Cesium中的成员变量和属性方法之间就是一个下划线_,比如成员变量为_id,对应的属性方法为id,这是Cesium中的一个规范。另外这些属性也是提供了set&get方法来进行赋值和取值,所以可以直接通过=运算符。下面我们由易到难,分别介绍。

    defineProperties

           Cesium.defineProperties本质上就是Object.defineProperties。我们不妨看一下它的定义:

    Object.defineProperties(obj, props)

           结合之前Entity.id代码,我们大概可以掌握通过Object.defineProperties封装多个属性的方式。通过下面这个范例,我们来强化一下理解:

    function obj(){
        this._id = undefined;
    }
    
    var o1 = new obj();
    
    Object.defineProperties(obj.prototype, { // or o1
      "property1": {
        //value: "Hello",
        //writable:true
        set: function(value){
            this._id = value;
        },
        get: function(){
            return this._id;
        }
      },
      "property2": {
        value: "Hello",
        writable: false,
        configurable:false
      }
      // etc. etc.
    });
    
    
    o1.property1 = 100;
    o1._id = 150;
    var nValue = o1.property1;
    o1.property2 = 200;// writable属性为false,赋值无效

           如上的代码中,我们创建了obj这个function,然后对obj.prototype这个对象创建了两个属性property1&property2,两个属性创建的方式略有不同,我是想通过这种不同来强调需要注意的地方,大家在编码的时候还是要遵守一种风格,保持一致性。首先,props参数,value,set&get,writable,confugurable以及enumerable。value和set&get都是用来赋值的,两种方式是有冲突的,二选一,两中方式略有不同,个人理解如果采用set&et,property1就是_id的引用。其次,writable,confugurable以及enumerable三个属性的默认值为false,具体的作用可以参考API,当然通过字面意思也不难理解。最后,defineProddrties的第一个参数为obj,所以可以使用原型或new一个obj出来,Cesium使用的是前者,用不用原型对应的原型链还是有一些差别的。下面是对比图,前者是obj+set&get的方式,set和get也是obj实例的,后者是prototype+set&get,property1为引用,而set&get是prototype的实例。想一下,假如是prototype+value呢?这个其实是prototype的内容,我们就不过于跑题了。

    compare

           这样,通过defineProperties的属性封装,我们保证了每一个Entity中,其原型中,对每一个属性都对应一个set和get方法的能力(需要与否则看实际情况),同时属性值对应function中的实例(而不是原型中提供该属性)。

    Property

           在进入createProperty这类方法前,我们先了解一下CesiumProperty,然后在一步步的展开。首先,个人认为这些Property在本质上和defineProperties并无本质的区别,defineProperties直接封装一些基本类型,如果是Object,这种accessor是引用的形式。因此,通过Property的封装,将引用或复制的权利交给Object的设计者,同时提供一些特殊功能,满足特殊的需求,比如SampledProperty提供插值功能,MaterialProperty则专门针对材质。这里我们主要通过两个有代表性的,Entity中涉及到的Property,了解一下Cesium中Property的实现。

    • ConstantProperty
    • CallbackProperty

    ConstantProperty

           如下是ConstantProperty的一个精简的代码片段,结合个人的注释,应该能对其有一个大概的认识:

    function ConstantProperty(value) {
        // 属性值
        this._value = undefined;
        // 是否提供克隆方法
        this._hasClone = false;
        // 是否提供判断相等的方法
        this._hasEquals = false;
        // 提供属性值变化的事件
        this._definitionChanged = new Event();
        this.setValue(value);
    }
    
    defineProperties(ConstantProperty.prototype, {
        // 属性是否为常量,只读
        isConstant : {
            value : true
        },
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        }
    });
    // 获取属性值的方法,如果提供clone方法,如果没有直接返回_value
    ConstantProperty.prototype.getValue = function(time, result) {
        return this._hasClone ? this._value.clone(result) : this._value;
    };
    // 赋值方法,判断是否提供了_hasClone&_hasEquals方法
    // 属性值变化会产生event事件
    ConstantProperty.prototype.setValue = function(value){}
    // 判断是否相等的方法
    // 如果_hasEquals为true,则会调用该对象的equal方法来判断
    ConstantProperty.prototype.equals = function(other) {}

          可见,相比基本类型,ConstantProperty是一个高级版的属性,首先在构造函数的参数中,value可以是一个基本类型,如此,该ConstantProperty会蜕化成defineProperties下直接封装的属性,当然,getValue,setValue以及equals方法本质上也是基本类型之间的赋值和对比,唯一不同的是提供了属性值变化的事件。如果value是一个obj,则用户可以提供clone以及equals方法,满足自己的特殊需要,当然你也可以不提供,那该obj就是默认的赋值和对比操作符。

    CallbackProperty

           下面是该属性的一个精简版,主要列出和ConstantProperity的不同处,方便对比。

    function CallbackProperty(callback, isConstant) {
        this._callback = undefined;
        this._isConstant = undefined;
        this._definitionChanged = new Event();
        this.setCallback(callback, isConstant);
    }
    defineProperties(CallbackProperty.prototype, {
        isConstant : {
            get : function() {
                return this._isConstant;
            }
        }
    });
    CallbackProperty.prototype.getValue = function(time, result) {
        return this._callback(time, result);
    };

           首先,ConstantProperity就是传进来一个value,返回该value,就是一个直来直去的思路。但有些时候事情并不是一成不变,比如在不同的状态下,希望返回不同的属性值。这种情况就需要提供回调方法,用户自己提供一个callback函数,CallbackProperty则通过setValue来绑定该回调方法,通过getValue来调用该方法,至于返回的属性值是什么,用户通过该callback方法自己负责。这就是CallbackProperty的应用场景。

    Property

           当然,除了ConstantProperity和CallbackProperty之外,Cesium还提供了CompositeProperty、SampledProperty、TimeIntervalCollectionProperty、MaterialProperty、PositionProperty、ReferenceProperty等。各个属性内部实现不一,但都会提供setValue&getValue等方法。当然,为了统一标准,Cesium提供了Property类,提供了一套接口来供用户调用:

    Property.equals = function(left, right)
    Property.arrayEquals = function(left, right)
    Property.isConstant = function(property)
    Property.getValueOrUndefined = function(property, time, result) {
        return defined(property) ? property.getValue(time, result) : undefined;
    };

           这样,用户可以不必过于纠结具体的property,通过Property提供的方法获取结果,比如上述代码中给出的Property.getValueOrUndefined,内部调用getValue获取属性值。

    createPropertyDescriptor

           当然,上述的这些Property还是处于Object类型,和obj._id还是同一个级别,和还需要经过封装,提供property1这样的形式,使其符合defineProperties规范,这样才能提供accessor的能力。这就是createProperty函数的作用:

    function createProperty(name, privateName, subscriptionName, configurable, createPropertyCallback) {
        var obj = {
            configurable : configurable,
            get : function() {
                return this[privateName];
            },
            set : function(value) {
                var oldValue = this[privateName];            
                value = createPropertyCallback(value);
    
                if (oldValue !== value) {
                    this[privateName] = value;
                    this._definitionChanged.raiseEvent(this, name, value, oldValue);
                }
    
                if (defined(value) && defined(value.definitionChanged)) {
                    this[subscriptionName] = value.definitionChanged.addEventListener(function() {
                        this._definitionChanged.raiseEvent(this, name, value, value);
                    }, this);
                }
            }
        };
        return obj;
    }

           如上就是createProperty方法的一个精简版,粗看一下,创建了一个obj并返回,而且该对象有三个成员变量:configurable,set&get(function),和之前介绍的defineProperties.props参数如出一辙,现在可以肯定这个方法的作用就是把各种Property加工成符合defineProperties规范的属性。当然这样的判断并不严谨,感觉的成分更大一些,我们结合Entity.description属性的创建过程具体了解一下。

    function Entity(options) {
        this._description = undefined;
    }
    
    defineProperties(Entity.prototype, {
        id : {
            get : function() {
                return this._id;
            }
        },
        description : createPropertyDescriptor('description')
    });

           如上是Entity.description属性,同时我还保留了id属性,用来参考。如果把createPropertyDescriptor函数替换为createProperty返回的obj,大概如下:

    function Entity(options) {
        this._description = undefined;
    }
    
    defineProperties(Entity.prototype, {
        id : {
            get : function() {
                return this._id;
            }
        },
        description : {
            configurable : configurable,
            get : function() {
                // ……
            },
            set : function(value) {
                // ……
            }
        };
    });

           这样就确定一定以及肯定了这个结论:createProperty就是返回一个符合defineProperties规范的属性。刨根问底的话,还是有两处疑问,首先,Entity.description调用的createPropertyDescriptor,又是如何传递到createProperty方法。其次,我们只是大概了了解了createProperty,内部具体做了什么还没有关注。逐个击破。

    createPropertyDescriptor('description');
    function createPropertyDescriptor(name, configurable, createPropertyCallback) {
        return createProperty(name, '_' + name.toString(), '_' + name.toString() + 'Subscription', defaultValue(configurable, false), defaultValue(createPropertyCallback, createConstantProperty));
    }
    function createConstantProperty(value) {
        return new ConstantProperty(value);
    }

            可见,description是该属性的accessor,对应Entity内部的属性变量是'_' + name.toString(),也就是_description,这个是Cesium默认的属性变量和属性accessor接口之间的规范。configurable默认为false,createPropertyCallback如果为null,则采用ConstantProperty类型,对应的是createConstantProperty回调函数,暴露了我C++程序员的身份。这样,createPropertyDescriptor就很自然的过渡到createProperty方法。接着,再看看createProperty内部的实现:

    function Entity(options) {
        this._description = undefined;
    }
    
    defineProperties(Entity.prototype, {
        id : {
            get : function() {
                return this._id;
            }
        },
        description : {
            configurable : false,
            get : function() {
                return this[_description];
            },
            set : function(value) {
                var oldValue = this[_description];
                var value = new ConstantProperty(value);
                
                if (oldValue !== value) {
                    this[_description] = value;
                    this._definitionChanged.raiseEvent(this, "description', value, oldValue);
                }
            }
        };
    });

           此时privatename就是_description,相比id属性,description属性本质上没有差别,只是多了一些繁枝缛节,多了一些异常判断,多了一些Event事件响应,当然还有对value的封装,比如description时,将value封装成了ConstantProperty类型,可以算的上是一个加强版的属性accessor方式。

           有了这样一套机制,不用写过多的代码,通过createPropertyDescriptor就可以很好的实现属性accessor的封装,代码间接,同时考虑了很多异常,并会有事件来满足用户的各种情况,提高稳定性。同时createPropertyCallback参数也提供了更多的选择,来满足我们的各类需求,或者没有需求,比如在Entity中,对属性accessor用如下三种形式:

    • createRawPropertyDescriptor
    • createPropertyTypeDescriptor
    • createPositionPropertyDescriptor

           有了上面的理解,我们大概做一下说明,应该不难理解他们的差别和不同的作用,我们在自己的使用场景中可以因地制宜。

    createRawPropertyDescriptor&createPositionPropertyDescriptor

    function createRawProperty(value) {
        return value;
    }
    
    function createRawPropertyDescriptor(name, configurable) {
        return createPropertyDescriptor(name, configurable, createRawProperty);
    }
    function createConstantPositionProperty(value) {
        return new ConstantPositionProperty(value);
    }
    
    function createPositionPropertyDescriptor(name) {
        return createPropertyDescriptor(name, undefined, createConstantPositionProperty);
    }

           这个没啥可说的吧,RawProperty比ConstantProperty还简单,人如其名,不加任何粉饰。同理createPositionPropertyDescriptor和createPropertyDescriptor也差不多,只是Property不是默认的ConstantProperty,而是ConstantPositionProperty。

    createPropertyTypeDescriptors

    rectangle : createPropertyTypeDescriptor('rectangle', RectangleGraphics)
    function createPropertyTypeDescriptor(name, Type) {
        return createPropertyDescriptor(name, undefined, function(value) {
            if (value instanceof Type) {
                return value;
            }
            return new Type(value);
        });
    }

           这个稍有不同,通过闭包的方式,这时对应的createPropertyCallback是用户自定义的,他甚至不是一个Property而是一个function类。我们可以这样理解,createPropertyDescriptor只是负责封装实现属性accessor,createPropertyCallback原意是让用户创建各类Property对象,但在Entity里面扩展了一下,并没用严格规定必须是Property类,而是可以用其他的function类,让createPropertyDescriptor的使用场景更广泛了。下面这种写法是否看起来就亲切很多,只是因为这类Graphics太多,所以Cesium用模版的思想抽象为Type参数,简化编码提高稳定,这也是值得我们学习的一点,Cesium在代码质量上的标准还是很严格的。

    function createRectangleGraphics(value) {
        return new RectangleGraphics(value);
    }
    function createPropertyTypeDescriptor(name) {
        return createPropertyDescriptor(name, undefined,createRectangleGraphics);
    }

    CallbackProperty

           刚才我们已经比较全面的介绍了Property相关的各类情况,尽管我们提到了CallbackProperty,但并没有涉及如何使用,有点遗忘的再回去看看CallbackProperty的实现,重点是getValue方法,我们下面通过一个例子,看看如何通过CallbackProperty实现自定义效果,范例链接

    function createDescriptionCallback() {
        var description = "Hello,World, ";
        return function(time, result) {        
            return description + time.toString();
        };
    }
    
    entity.description = new Cesium.CallbackProperty(createDescriptionCallback(), true);
    
    Property.getValueOrUndefined(entity.description,time);

           当然,得益于之前Entity.description通过createPropertyDescriptor提供了accessor接口,这里,等号运算符会调用set方法,只是将oldValue(ConstantProperty)更改为value(CallbackProperty)。此时,内部通过Property.getValueOrUndefined->CallbackProperty.getValue->this._callback,实现回调函数的调用。

    总结

           如上是Cesium在Property模块的一些实现原理和使用方式,我们简单总结一下知识点如下,不知道大家有什么收获,是否能够从Cesium优雅的设计和封装中学到什么奇技淫巧。

    • Property
    • ConstantProperty
    • CallbackProperty
    • defineProperties
    • createPropertyDescriptor
    • createRawPropertyDescriptor
    • createPositionPropertyDescriptor
    • createPropertyTypeDescriptor

  • 相关阅读:
    无限维
    黎曼流形
    why we need virtual key word
    TOJ 4119 Split Equally
    TOJ 4003 Next Permutation
    TOJ 4002 Palindrome Generator
    TOJ 2749 Absent Substrings
    TOJ 2641 Gene
    TOJ 2861 Octal Fractions
    TOJ 4394 Rebuild Road
  • 原文地址:https://www.cnblogs.com/fuckgiser/p/6142824.html
Copyright © 2011-2022 走看看