[JavaScript]对象关联风格与行为委托模式
对象关联.
“[[prototype)]的机制就是存在于对象中的一个内部链接,它会引用其它对象”
为何创建原型链(关联)?
Object.create()会创建一个新对象,参数对象会作为新对象的__proto__.
“我们并不需要类来创建两个对象之间的关系,只需要通过委托来关联对象就足够了.而Object.create()不包含’类的诡计’,所以它可以完美地创建我们想要的关联关系”
关联关系是备用吗
“但是如果你这样写只是为了让对象在无法处理属性或方法时有备用对象可查,那么你的软件就会变得有点而且很难理解和维护.”
设想一个对象中明明不存在一个方法,却可以调用该方法,这些似乎凭空冒出的方法属性令人费解,应当避免"直接委托",使用"内部委托".
对象关联风格
定义一个名为Task的对象,包含所有任务都可以委托的基本行为(不存数据,只有可共用的函数).
对于每个任务,再定义专门的对象来存储数据与行为.
把各个专用任务对象关联到Task对象,需要时在专用对象中调用方法.
Task = {setID: function(ID) {this.id = ID};
}
Operation0 = Object.create(Task);
Operation0.doTask = function(ID, Label) { this.setID(ID);this.label = Label;
};
Operation1 = Object.create(Task);
Operation1.doTask = function() {console.log(this.label);
};
这种编码风格称为"对象关联"(OLOO,Objest Linked To Other Objects).
①这种风格中,数据当存储于专用对象中,即"在[[prototype]]委托中最好把状态保存在委托者而非受托者上.
②"尽量避免在[[prototype]]链的不同级别中使用相同命名,否则会发生引用歧义."
尽量少使用容易被重写的通用方法名,提倡使用更有描述性的方法名,尤其要写清相应对象行为的类型.因为方法名不仅在定义的位置,而是贯穿整篇代码.
③调用方法时会触发this隐式绑定,因此公用对象方法虽身处公用对象,运行时this仍绑定到专用对象.
委托设计模式
“委托行为意味着某些对象在找不到属性/方法时会把这个请求委托给另一对象(公用对象).”
Task = {set:...,push:...,unshift:..,shift:...,pop:...
};
Operation0 = Object.create(Task);
Operation0.push0 = function(a, b, c) {this.set();this.push(a, b, c);
}
Operation1 = Object.create(Task);
Operation1.shift1 = function(a,b) {this.set();this.shift(a, b);
}
用这模式的前提是你的各个子类真的结构相似,可以用基类中的公用方法处理.
不然你基础对象。里只能写一些很基础的方法提供给专用对象,专用对象还要为了一个操作把各种公用方法搓在一起,专用对象中仍有大量工作,那基础对象就鸡肋了,意义不大, 省不下多少工作量.对象关联模式的几个原则:
专用对象关联到基础对象, 用到方法直接原型链查.
不重写,利用公用方法构建专用方法,每个方法不同名.
不要跳过专用对象直接调公用方法.
委托模式下对象间的关系
书作者鼓励在这个模式下把所有对象都看做平级的兄弟,没有父子级概念——其实你要是硬要分个父子出来,那会很难受了.
比如我从人对象关联到了男人,然后为了让男女能够相爱,女 = Object.create(男),这确实省下了不少代码,女对象里调用一些男对象的方法就能完成这个操作,但是女对象应该能调用男对象的方法吗?不能吧.
或许有些极端了,这个操作也可以再藉由人创建女,让男女都利用人基类的方法完成交流.
就像下面这样:
const component = {init: function (width, height) {this.width = width;this.height = height;this dom = null;},insert: function(parentNode) {if(this.dom) {this.dom.style.width = `${ this.width }px`;this.dom.style.height = `{ this.height }px`;parentNode.appendchild( this.dom);}}
}
const Button = Object.create(component);
Button.setup = function(width, height, label) {this.init(width, height);this.label = label;this.dom = document. createElement("button");this.dom.innerText(this.Label);
}
Button.build= function (parentNode) {this.insert(parentNode);this.dom.onclick = (e) {this.onClick(e);}
}
Button.onClick = function(e) {console.log ('Button ${ this.label } clicked.');
};
const btn = Object.create(Button); btn.setup(25,30,'btn');
btn.build(document.getElementById('aa');
还顺眼吧,现在他们的父子关系还能对应基础对象和专用对象.
但书中明确给出了例子让同级对象直接关联委托,这在功能上就像把两个同级对象写在一个对象里,你在受托者那里真是能完全访问委托者的this.
受托者的方法也基于委托人的方法构建,一方高度依赖于另一方,它俩可是兄弟对象,本该都依赖共同的基类,现在一方却高度依赖另一方!这就像"我生下了我的兄弟"一样.
下面是书中给出的两个兄弟对象的例子:
var LoginController = {errors:[],getUser: function() (return document.getElenentById("login_usernane").value;getPassword: function()(return document,getElementById("login_password").value;},validateEntry: functlon(user, pw) {user = user || this.getuser();pw = pw || this.getPassword();if(!(user && pw)) (return this.failure("Please enter a usernane & password!");} else if(pw.length < 5) {return this.failure("Password must be 5+ characters!");}return true;},showDialog: function(title,msg) {// 给用户显示标题和消息 }failure: function(err){this.errors.push(err);this.showDialog("Error", "Login invalid:" + err);};
};
// 让AuthController委托LoginController
var AuthController = Object.create(LoginController); AuthController.errors = [];
AuthController.checkAuth = function() {var user = this.getUser();var pw = this.getPassword();if(this.validateEntry(user, pw)) {this.server("/check-auth",{user: user,pw: pw}).then( this.accepted.bind(this)).fail( this.rejected.bind(this));}
};
AuthController.server = function(url,data) {return $.ajax({url: url,data: data})
};
AuthController.accepted = function() {this.showDialog("Success", "Authenticated!")
};
AuthController.rejected = function(err){this.failure("Auth Failed:" + err);
};
你可以看到,是利用LoginController创建了AuthController,而这两者本应是同级,现在后者极度依赖前者,反倒弄的像是父子关系.
也许想开了,跳出面向对象的思维,也就觉得这模式好了,至少我现在还是习惯面向对象的风格,严格的等级在代码里体现.
用面向对象的思维去看,这简直就是在犯规.