// example duck typing method
var hasMethods = function(obj /*, method list as strings */){
var i = 1, methodName;
while((methodName = arguments[i++])){
if(typeof obj[methodName] != 'function') {
return false;
}
}
return true;
}
// in your code
if(hasMethods(obj, 'quak', 'flapWings','waggle')) {
// IT'S A DUCK, do your duck thang
}
// Create a 'Dog' class that inherits from 'Animal'
// and implements the 'Mammal' interface
var Dog = Object.extend(Animal, {
constructor: function(name) {
Dog.superClass.call(this, name);
},
bark: function() {
alert('woof');
}
}).implement(Mammal);
Function.prototype.implement = function() {
// Loop through each interface passed in and then check
// that its members are implemented in the context object (this).
for(var i = 0; i < arguments.length; i++) {
// .. Check member's logic ..
}
// Remember to return the class being tested
return this;
}
latLngLiteral = new Proxy({},{
set: function(obj, prop, val) {
//only these two properties can be set
if(['lng','lat'].indexOf(prop) == -1) {
throw new ReferenceError('Key must be "lat" or "lng"!');
}
//the dec format only accepts numbers
if(typeof val !== 'number') {
throw new TypeError('Value must be numeric');
}
//latitude is in range between 0 and 90
if(prop == 'lat' && !(0 < val && val < 90)) {
throw new RangeError('Position is out of range!');
}
//longitude is in range between 0 and 180
else if(prop == 'lng' && !(0 < val && val < 180)) {
throw new RangeError('Position is out of range!');
}
obj[prop] = val;
return true;
}
});
class LatLngLiteral {
constructor(props)
{
this.proxy = new Proxy(this, {
set: function(obj, prop, val) {
//only these two properties can be set
if(['lng','lat'].indexOf(prop) == -1) {
throw new ReferenceError('Key must be "lat" or "lng"!');
}
//the dec format only accepts numbers
if(typeof val !== 'number') {
throw new TypeError('Value must be numeric');
}
//latitude is in range between 0 and 90
if(prop == 'lat' && !(0 < val && val < 90)) {
throw new RangeError('Position is out of range!');
}
//longitude is in range between 0 and 180
else if(prop == 'lng' && !(0 < val && val < 180)) {
throw new RangeError('Position is out of range!');
}
obj[prop] = val;
return true;
}
})
return this.proxy
}
}
class LatLngLiteral {
#latitude;
#longitude;
get lat()
{
return this.#latitude;
}
get lng()
{
return this.#longitude;
}
set lat(val)
{
//the dec format only accepts numbers
if(typeof val !== 'number') {
throw new TypeError('Value must be numeric');
}
//latitude is in range between 0 and 90
if(!(0 < val && val < 90)) {
throw new RangeError('Position is out of range!');
}
this.#latitude = val
}
set lng(val)
{
//the dec format only accepts numbers
if(typeof val !== 'number') {
throw new TypeError('Value must be numeric');
}
//longitude is in range between 0 and 180
if(!(0 < val && val < 180)) {
throw new RangeError('Position is out of range!');
}
this.#longitude = val
}
}
/**
@parameter {Array|object} required : method name list or members types by their name
@constructor
*/
let Interface=function(required){
this.obj=0;
if(required instanceof Array){
this.obj={};
required.forEach(r=>this.obj[r]='function');
}else if(typeof(required)==='object'){
this.obj=required;
}else {
throw('Interface invalid parameter required = '+required);
}
};
/** check constructor instance
@parameter {object} scope : instance to check.
@parameter {boolean} [strict] : if true -> throw an error if errors ar found.
@constructor
*/
Interface.prototype.check=function(scope,strict){
let err=[],type,res={};
for(let k in this.obj){
type=typeof(scope[k]);
if(type!==this.obj[k]){
err.push({
key:k,
type:this.obj[k],
inputType:type,
msg:type==='undefined'?'missing element':'bad element type "'+type+'"'
});
}
}
res.success=!err.length;
if(err.length){
res.msg='Class bad structure :';
res.errors=err;
if(strict){
let stk = new Error().stack.split('\n');
stk.shift();
throw(['',res.msg,
res.errors.map(e=>'- {'+e.type+'} '+e.key+' : '+e.msg).join('\n'),
'','at :\n\t'+stk.join('\n\t')
].join('\n'));
}
}
return res;
};
使用实例:
// create interface tool
let dataInterface=new Interface(['toData','fromData']);
// abstract constructor
let AbstractData=function(){
dataInterface.check(this,1);// check extended element
};
// extended constructor
let DataXY=function(){
AbstractData.apply(this,[]);
this.xy=[0,0];
};
DataXY.prototype.toData=function(){
return [this.xy[0],this.xy[1]];
};
// should throw an error because 'fromData' is missing
let dx=new DataXY();
与类
class AbstractData{
constructor(){
dataInterface.check(this,1);
}
}
class DataXY extends AbstractData{
constructor(){
super();
this.xy=[0,0];
}
toData(){
return [this.xy[0],this.xy[1]];
}
}