JavaScript面向对象的设计原则(二)

tiantts 6年前

来自: http://www.cnblogs.com/jackyKin/p/5183463.html

这一期,我们来看下JavaScript面向对象设计中的核心--对象.面向对象的本质是将现实世界抽象层到计算机的世界,Class所体现的正是人类在观察世界中所经常进行的一项工作,分类.

我们可能会经常整理屋子,然后将整理后的东西分门别类的放好.我们根据什么分类?电器,食物,书籍,这些属性是我们分类的依据,这些不同类别的用品都有什么功能?食物可以吃,可以加工,书籍可以看,电器可以使用.抽象到计算机的世界,于是就有了class,property,method,类,属性,方法.

所以我们日常中所进行的活动,很多都是分类的活动,你在工作中生活中所做的分类活动就是一种面向对象的设计行为,相对于类的概念,对象更具体,粒度更小,我们可以将具有一类共性的对象称为类.

当一个对象很具体,我们就称其为一个实例.

示例

var personInstance = {    name:'jacky',    age:60,    six:'meal'  }

当一个对象很抽象,我们可以称其为类.

示例

var PersonClass = {    name:'',    age:'',    six:''  }

这里我们没有使用任何JavaScript模拟类的技术,也没有用上ES6的class,但实际上这就是一个实例,一个类,从中我们可以看到,面向对象的设计其实是语言无关的,只要你能够表达出其中的含义,如何实现,并不是重点.

JavaScript类设计

抛开各种模拟类的语法糖,让我们直面本质来看看如何设计一个JavaScript类.

1.合理封装

封装的目的是找出代码中的变化点,将其包裹起来,通过统一的具有语义的API接口来输出结果,隐藏其中的实现和具体算法.

示例

function MenuTree () {      this.dataSource = [],      this.template = '<div id="menu-tree"><div>',      this.element = null,      this.init = function(domString){          var scope = this;          scope.element = document.querySelector(domString);          scope.render = function () {              scope.element.innerHTML = scope.template;          };          return scope;      }  }  //for example  new MenuTree().init(domString).render();

这里我们封装了一个MenuTree的类,这是一个用来初始化树形菜单的类,我们这么写问题不大,当然假设需求也很简单,模板是固定的,于是我们只要传入一个domString来将一个dom节点变成一个MenuTree就完成了,对使用者来说,他看代码就知道嗯,这个类使用init方法初始化,然后返回一个包含了render方法的对象,这个对象保存了对dom节点的引用,从而通过render方法来将模板写入节点.不需要过多的文档注释,代码能够表达这个类的含义.

封装也是我们对旧代码不断重构的利器,不断的抽象出接口来推动软件系统的升级,基于封装的例子我们再来看看如何进行演化和重构.

随着软件的扩大,我们需要用到很多其他的类于是我们又设计了一个List类:

示例

function List () {      this.dataSource = [],      this.template = '<div id="list"><div>',      this.element = null,      this.init = function(domString){          var scope = this;          scope.element = document.querySelector(domString);          scope.onBeforeRender();          scope.render = function () {              scope.element.innerHTML = scope.template;              scope.onAfterRender();          };          return scope;      },      this.onBeforeRender = function(){          code...       },      this.onAfterRender = function(){          code...      }  }

然后你会发现List类和MenuTree类有相似的地方,但是需求上更复杂,需要在render前后做些逻辑处理,于是我们将其重构,对List和MenuTree进行一次抽象,从而得到一个UI类.

UI类示例

function UI(constructor) {      this.dataSource = [];      this.template = constructor.template || '';      this.element = null;      this.onBeforeRender = constructor.onBeforeRender || function () {              };      this.onAfterRender = constructor.onAfterRender || function () {              };  }  UI.prototype = {      init: function (domString) {          var scope = this;          scope.element = document.querySelector(domString);          scope.onBeforeRender();          scope.render = function () {              scope.element.innerHTML = scope.template;              scope.onAfterRender();          };          return scope;      }  };

List重构示例

function List() {      UI.apply(this, arguments)  }  temp = List.prototype.constructor;  List.prototype = UI.prototype;  List.prototype.constructor = temp;    var myList = new List({      template: '<h1>hello world</h1>',      onBeforeRender: function () {          //code...      },      onAfterRender: function () {          //code...      }  });  myList.init('ui-list').render();

MenuTree重构示例

function MenuTree(){      UI.apply(this, arguments);  }  temp = List.prottype.constructor;  ....    var myMenu = new Menu()  ....

JavaScript重构的核心就是抽象出对象的共享属性和方法,至于如何实现继承,方法很多,ES6也提供了class的语法糖,大家可参考相关的文章和资料,但是核心在于如何设计抽象基类.

2.基于职责设计

类的职责值得是类实现的功能和承担的数据处理任务

OOD的设计原则:

  1. 每个类的职责必须明确
  2. 每个类只承担一项职责

避免过大的类,指那些能干一切事的类,他们就像上帝一样,我们称他们为God Object这里罗列一些GRASP(General Responsibility Assignment Software Pattern)原则:

  1. Creater
  2. Controller
  3. Pure Fabrication
  4. Information Expert
  5. High Cohesion
  6. Indirection
  7. Low Coupling
  8. Polymorphism
  9. Protected Variations

Expert/Information Expert原则:

某些功能需要某些必要的信息,这些信息在哪个类中,就让这个类承担这项职责

在一些开发场景中,我们可能需要构造一个购物车用来存放用户选择的商品,我们可以称为ShoppingCart类

对于用户选择的商品数量,商品价格等信息,均应该归属ShoppingCart类而不是前置的Customer类.

</div>

Creator原则

JavaScript中类本身也是对象,不过函数对象与普通的实例对象并不相同.关于这个原则,我们只要确保代码中构造对象的一致性即可

Conroller原则

前端对于MVC以及其各种变种应该是耳熟能详了,尤其在web开发领域,基于时间驱动模型的MVC非常适合用来开发各种web应用,但我们可能很熟悉View和Model的概念,但是对于Controller可能就很难去定义了.

一般我们对于Controller代码的编写基于这几点,所谓Controller仅仅是控制者,调度者的概念,对于调度物体的内部实现不应该关心也不应该去干预,Controller仅仅应当包含从View到Model的访问代码以及Model返回给View的数据代码,不包含具体的业务逻辑,所谓业务逻辑,就是你的软件功能.

示例

//view  var indexView = app.createView({      template:'<div>{name}</div>',      onBeforeRender:function(data){          //code....          return data;      }  })  //model  var indexModel = app.createModel({      getData:$.post(url,function(){})        })  //controller  app.createController('indexController',function(){      indexModel.getData().then(function(data){          indexView.setData(data)      })  })

对于返回data的处理应当在indexView中的onBeforeRender中处理,而不应当去controller中ajax的回调中编写.这就是controller原则.

High Cohesion/Low Coupling

高内聚低耦合,含义很深啊,自己体会吧,骚年们,不过建议还是有的,记住组合优先于继承,那你的类就会高内聚低耦合.

Indirection(间接性)原则

对象之间相互关联,会形成复杂的交互网络,为减少耦合,通过创建一个中间对象将多对多的关系拆散为一对多关联.

Pure Fabrication原则

当某些功能很难找到特定归属的业务逻辑相关的类时,就创建一个新类,这是一个折衷的办法,折衷类我们称为Pure Fabrication,就是生造出来的类,惯例我们称为XXXXXService类.这也是实现低耦合高内聚的一种方法

Protected Variations原则

这个原则称为隔离变化,简单的说,当我们设计一个JavaScript类时应该考虑,如果上下文环境变换,这个类还能正常工作么?.简单的例子,如果你在一个UI类中对dom结构强要求,那么就很容挂了.

Polymorphism 多态性原则

如果一个类中对于不同状态的实现依赖于大量的switch和if/else if判断,那就应该考虑通过继承的方式,让不同的子类去处理.

写着写着就下班了,祝大家新年快乐!

</div>