第六章: 行为委托 - 更简单的设计
更简单的设计
OLOO 除了提供表面上更简单(而且更灵活!)的代码之外,行为委托作为一个模式实际上会带来更简单的代码架构。让我们讲解最后一个例子来说明 OLOO 是如何简化你的整体设计的。
这个场景中我们将讲解两个控制器对象,一个用来处理网页的登录 form(表单),另一个实际处理服务器的认证(通信)。
我们需要帮助工具来进行与服务器的 Ajax 通信。我们将使用 JQuery(虽然其他的框架都可以),因为它不仅为我们处理 Ajax,而且还返回一个类似 Promise 的应答,这样我们就可以在代码中使用 .then(..)
来监听这个应答。
注意: 我们不会再这里讲到 Promise,但我们会在以后的 你不懂 JS 系列中讲到。
根据典型的类的设计模式,我们在一个叫做 Controller
的类中将任务分解为基本功能,之后我们会衍生出两个子类,LoginController
和 AuthController
,它们都继承自 Controller
而且特化某些基本行为。
// 父类
function Controller() {
this.errors = [];
}
Controller.prototype.showDialog = function(title,msg) {
// 在对话框中给用户显示标题和消息
};
Controller.prototype.success = function(msg) {
this.showDialog( "Success", msg );
};
Controller.prototype.failure = function(err) {
this.errors.push( err );
this.showDialog( "Error", err );
};
// 子类
function LoginController() {
Controller.call( this );
}
// 将子类链接到父类
LoginController.prototype = Object.create( Controller.prototype );
LoginController.prototype.getUser = function() {
return document.getElementById( "login_username" ).value;
};
LoginController.prototype.getPassword = function() {
return document.getElementById( "login_password" ).value;
};
LoginController.prototype.validateEntry = function(user,pw) {
user = user || this.getUser();
pw = pw || this.getPassword();
if (!(user && pw)) {
return this.failure( "Please enter a username & password!" );
}
else if (pw.length < 5) {
return this.failure( "Password must be 5+ characters!" );
}
// 到这里了?输入合法!
return true;
};
// 覆盖来扩展基本的 `failure()`
LoginController.prototype.failure = function(err) {
// "super"调用
Controller.prototype.failure.call( this, "Login invalid: " + err );
};
// 子类
function AuthController(login) {
Controller.call( this );
// 除了继承外,我们还需要合成
this.login = login;
}
// 将子类链接到父类
AuthController.prototype = Object.create( Controller.prototype );
AuthController.prototype.server = function(url,data) {
return $.ajax( {
url: url,
data: data
} );
};
AuthController.prototype.checkAuth = function() {
var user = this.login.getUser();
var pw = this.login.getPassword();
if (this.login.validateEntry( user, pw )) {
this.server( "/check-auth",{
user: user,
pw: pw
} )
.then( this.success.bind( this ) )
.fail( this.failure.bind( this ) );
}
};
// 覆盖以扩展基本的 `success()`
AuthController.prototype.success = function() {
// "super"调用
Controller.prototype.success.call( this, "Authenticated!" );
};
// 覆盖以扩展基本的 `failure()`
AuthController.prototype.failure = function(err) {
// "super"调用
Controller.prototype.failure.call( this, "Auth Failed: " + err );
};
var auth = new AuthController(
// 除了继承,我们还需要合成
new LoginController()
);
auth.checkAuth();
我们有所有控制器分享的基本行为,它们是 success(..)
,failure(..)
和 showDialog(..)
。我们的子类 LoginController
和 AuthController
覆盖了 failure(..)
和 success(..)
来增强基本类的行为。还要注意的是,AuthController
需要一个 LoginController
实例来与登录 form 互动,所以它变成了一个数据属性成员。
另外一件要提的事情是,我们选择一些 合成 散布在继承的顶端。AuthController
需要知道 LoginController
,所以我们初始化它(new LoginController()
),并用一个称为 this.login
的类属性成员来引用它,这样 AuthController
才可以调用 LoginController
上的行为。
注意: 这里可能会存在一丝冲动,就是使 AuthController
继承 LoginController
,或者反过来,这样的话我们就会通过继承链得到 虚拟合成。但是这是一个非常清晰的例子,表明对这个问题来讲,将类继承作为模型有什么问题,因为 AuthController
和 LoginController
都不特化对方的行为,所以它们之间的继承没有太大的意义,除非类是你唯一的设计模式。与此相反的是,我们在一些简单的合成中分层,然后它们就可以合作了,同时它俩都享有继承自父类 Controller
的好处。
如果你熟悉面向类(OO)的设计,这都应该看起来十分熟悉和自然。
去类化
但是,我们真的需要用一个父类,两个子类,和一些合成来对这个问题建立模型吗?有办法利用 OLOO 风格的行为委托得到 简单得多 的设计吗?有的!
var LoginController = {
errors: [],
getUser: function() {
return document.getElementById( "login_username" ).value;
},
getPassword: function() {
return document.getElementById( "login_password" ).value;
},
validateEntry: function(user,pw) {
user = user || this.getUser();
pw = pw || this.getPassword();
if (!(user && pw)) {
return this.failure( "Please enter a username & 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 );
};
因为 AuthController
只是一个对象(LoginController
也是),我们不需要初始化(比如 new AuthController()
)就能执行我们的任务。所有我们要做的是:
AuthController.checkAuth();
当然,通过 OLOO,如果你确实需要在委托链上创建一个或多个附加的对象时也很容易,而且仍然不需要任何像类实例化那样的东西:
var controller1 = Object.create( AuthController );
var controller2 = Object.create( AuthController );
使用行为委托,AuthController
和 LoginController
仅仅是对象,互相是 水平 对等的,而且没有被安排或关联成面向类中的父与子。我们有些随意地选择让 AuthController
委托至 LoginController
—— 相反方向的委托也同样是有效的。
第二个代码段的主要要点是,我们只拥有两个实体(LoginController
and AuthController
),而 不是之前的三个。
我们不需要一个基本的 Controller
类来在两个子类间“分享”行为,因为委托是一种可以给我们所需功能的,足够强大的机制。同时,就像之前注意的,我们也不需要实例化我们的对象来使它们工作,因为这里没有类,只有对象自身。 另外,这里不需要 合成 作为委托来给两个对象 差异化 地合作的能力。
最后,由于没有让名称 success(..)
和 failure(..)
在两个对象上相同,我们避开了面向类的设计的多态陷阱:它将会需要难看的显式假想多态。相反,我们在 AuthController
上称它们为 accepted()
和 rejected(..)
—— 对于它们的具体任务来说,稍稍更具描述性的名称。
底线: 我们最终得到了相同的结果,但是用了(显著的)更简单的设计。这就是 OLOO 风格代码和 行为委托 设计模式的力量。