更简单的设计

OLOO 除了提供表面上更简单(而且更灵活!)的代码之外,行为委托作为一个模式实际上会带来更简单的代码架构。让我们讲解最后一个例子来说明 OLOO 是如何简化你的整体设计的。

这个场景中我们将讲解两个控制器对象,一个用来处理网页的登录 form(表单),另一个实际处理服务器的认证(通信)。

我们需要帮助工具来进行与服务器的 Ajax 通信。我们将使用 JQuery(虽然其他的框架都可以),因为它不仅为我们处理 Ajax,而且还返回一个类似 Promise 的应答,这样我们就可以在代码中使用 .then(..) 来监听这个应答。

注意: 我们不会再这里讲到 Promise,但我们会在以后的 你不懂 JS 系列中讲到。

根据典型的类的设计模式,我们在一个叫做 Controller 的类中将任务分解为基本功能,之后我们会衍生出两个子类,LoginControllerAuthController,它们都继承自 Controller 而且特化某些基本行为。

  1. // 父类
  2. function Controller() {
  3. this.errors = [];
  4. }
  5. Controller.prototype.showDialog = function(title,msg) {
  6. // 在对话框中给用户显示标题和消息
  7. };
  8. Controller.prototype.success = function(msg) {
  9. this.showDialog( "Success", msg );
  10. };
  11. Controller.prototype.failure = function(err) {
  12. this.errors.push( err );
  13. this.showDialog( "Error", err );
  14. };
  1. // 子类
  2. function LoginController() {
  3. Controller.call( this );
  4. }
  5. // 将子类链接到父类
  6. LoginController.prototype = Object.create( Controller.prototype );
  7. LoginController.prototype.getUser = function() {
  8. return document.getElementById( "login_username" ).value;
  9. };
  10. LoginController.prototype.getPassword = function() {
  11. return document.getElementById( "login_password" ).value;
  12. };
  13. LoginController.prototype.validateEntry = function(user,pw) {
  14. user = user || this.getUser();
  15. pw = pw || this.getPassword();
  16. if (!(user && pw)) {
  17. return this.failure( "Please enter a username & password!" );
  18. }
  19. else if (pw.length < 5) {
  20. return this.failure( "Password must be 5+ characters!" );
  21. }
  22. // 到这里了?输入合法!
  23. return true;
  24. };
  25. // 覆盖来扩展基本的 `failure()`
  26. LoginController.prototype.failure = function(err) {
  27. // "super"调用
  28. Controller.prototype.failure.call( this, "Login invalid: " + err );
  29. };
  1. // 子类
  2. function AuthController(login) {
  3. Controller.call( this );
  4. // 除了继承外,我们还需要合成
  5. this.login = login;
  6. }
  7. // 将子类链接到父类
  8. AuthController.prototype = Object.create( Controller.prototype );
  9. AuthController.prototype.server = function(url,data) {
  10. return $.ajax( {
  11. url: url,
  12. data: data
  13. } );
  14. };
  15. AuthController.prototype.checkAuth = function() {
  16. var user = this.login.getUser();
  17. var pw = this.login.getPassword();
  18. if (this.login.validateEntry( user, pw )) {
  19. this.server( "/check-auth",{
  20. user: user,
  21. pw: pw
  22. } )
  23. .then( this.success.bind( this ) )
  24. .fail( this.failure.bind( this ) );
  25. }
  26. };
  27. // 覆盖以扩展基本的 `success()`
  28. AuthController.prototype.success = function() {
  29. // "super"调用
  30. Controller.prototype.success.call( this, "Authenticated!" );
  31. };
  32. // 覆盖以扩展基本的 `failure()`
  33. AuthController.prototype.failure = function(err) {
  34. // "super"调用
  35. Controller.prototype.failure.call( this, "Auth Failed: " + err );
  36. };
  1. var auth = new AuthController(
  2. // 除了继承,我们还需要合成
  3. new LoginController()
  4. );
  5. auth.checkAuth();

我们有所有控制器分享的基本行为,它们是 success(..)failure(..)showDialog(..)。我们的子类 LoginControllerAuthController 覆盖了 failure(..)success(..) 来增强基本类的行为。还要注意的是,AuthController 需要一个 LoginController 实例来与登录 form 互动,所以它变成了一个数据属性成员。

另外一件要提的事情是,我们选择一些 合成 散布在继承的顶端。AuthController 需要知道 LoginController,所以我们初始化它(new LoginController()),并用一个称为 this.login 的类属性成员来引用它,这样 AuthController 才可以调用 LoginController 上的行为。

注意: 这里可能会存在一丝冲动,就是使 AuthController 继承 LoginController,或者反过来,这样的话我们就会通过继承链得到 虚拟合成。但是这是一个非常清晰的例子,表明对这个问题来讲,将类继承作为模型有什么问题,因为 AuthControllerLoginController 都不特化对方的行为,所以它们之间的继承没有太大的意义,除非类是你唯一的设计模式。与此相反的是,我们在一些简单的合成中分层,然后它们就可以合作了,同时它俩都享有继承自父类 Controller 的好处。

如果你熟悉面向类(OO)的设计,这都应该看起来十分熟悉和自然。

去类化

但是,我们真的需要用一个父类,两个子类,和一些合成来对这个问题建立模型吗?有办法利用 OLOO 风格的行为委托得到 简单得多 的设计吗?有的!

  1. var LoginController = {
  2. errors: [],
  3. getUser: function() {
  4. return document.getElementById( "login_username" ).value;
  5. },
  6. getPassword: function() {
  7. return document.getElementById( "login_password" ).value;
  8. },
  9. validateEntry: function(user,pw) {
  10. user = user || this.getUser();
  11. pw = pw || this.getPassword();
  12. if (!(user && pw)) {
  13. return this.failure( "Please enter a username & password!" );
  14. }
  15. else if (pw.length < 5) {
  16. return this.failure( "Password must be 5+ characters!" );
  17. }
  18. // 到这里了?输入合法!
  19. return true;
  20. },
  21. showDialog: function(title,msg) {
  22. // 在对话框中向用于展示成功消息
  23. },
  24. failure: function(err) {
  25. this.errors.push( err );
  26. this.showDialog( "Error", "Login invalid: " + err );
  27. }
  28. };
  1. // 链接`AuthController`委托到`LoginController`
  2. var AuthController = Object.create( LoginController );
  3. AuthController.errors = [];
  4. AuthController.checkAuth = function() {
  5. var user = this.getUser();
  6. var pw = this.getPassword();
  7. if (this.validateEntry( user, pw )) {
  8. this.server( "/check-auth",{
  9. user: user,
  10. pw: pw
  11. } )
  12. .then( this.accepted.bind( this ) )
  13. .fail( this.rejected.bind( this ) );
  14. }
  15. };
  16. AuthController.server = function(url,data) {
  17. return $.ajax( {
  18. url: url,
  19. data: data
  20. } );
  21. };
  22. AuthController.accepted = function() {
  23. this.showDialog( "Success", "Authenticated!" )
  24. };
  25. AuthController.rejected = function(err) {
  26. this.failure( "Auth Failed: " + err );
  27. };

因为 AuthController 只是一个对象(LoginController 也是),我们不需要初始化(比如 new AuthController())就能执行我们的任务。所有我们要做的是:

  1. AuthController.checkAuth();

当然,通过 OLOO,如果你确实需要在委托链上创建一个或多个附加的对象时也很容易,而且仍然不需要任何像类实例化那样的东西:

  1. var controller1 = Object.create( AuthController );
  2. var controller2 = Object.create( AuthController );

使用行为委托,AuthControllerLoginController 仅仅是对象,互相是 水平 对等的,而且没有被安排或关联成面向类中的父与子。我们有些随意地选择让 AuthController 委托至 LoginController —— 相反方向的委托也同样是有效的。

第二个代码段的主要要点是,我们只拥有两个实体(LoginController and AuthController),而 不是之前的三个

我们不需要一个基本的 Controller 类来在两个子类间“分享”行为,因为委托是一种可以给我们所需功能的,足够强大的机制。同时,就像之前注意的,我们也不需要实例化我们的对象来使它们工作,因为这里没有类,只有对象自身。 另外,这里不需要 合成 作为委托来给两个对象 差异化 地合作的能力。

最后,由于没有让名称 success(..)failure(..) 在两个对象上相同,我们避开了面向类的设计的多态陷阱:它将会需要难看的显式假想多态。相反,我们在 AuthController 上称它们为 accepted()rejected(..) —— 对于它们的具体任务来说,稍稍更具描述性的名称。

底线: 我们最终得到了相同的结果,但是用了(显著的)更简单的设计。这就是 OLOO 风格代码和 行为委托 设计模式的力量。