资讯专栏INFORMATION COLUMN

Javascript 设计模式读书笔记(二)——封装,简单的创建对象模式

lentrue / 3170人阅读

摘要:创建对象中,创建对象的基本模式有三种。因此,在设计构造函数时,需要进行慎重考虑。因此在中,这种问题被称作继承破坏封装。静态成员每个只有一份,直接通过类对象进行访问。

什么是封装

找工作时一些公司给了offer后我就想知道真正拿到手的是多少,毕竟赋税繁重。但各种税也好,五险一金也好我实在是弄不清楚,于是我就会在网上的一些税后收入计算器上进行计算,只需要填写一些基本信息,比如税前收入,所在地区等等,就能够获得详细的结果,包括各种税收的详细数值。在这个过程中,我只是按照接口给定的要求进行了数据的输入,具体计算过程我并不知道。也就是说,在这个程序内部,数据表现形式和实现细节是隐藏的,这在某种意义上也是封装的一种。

在Javascript中,对象中的细节有时也需要隐藏,但JS不像其他的静态语言,比如Java,一样,有private这样的关键字。那么在Javascript中,就可以用闭包的概念来创建只能从对象内部访问的方法和属性。

在使用接口实现信息隐藏的过程中,同时也是使用了接口的概念。好比火影里的通灵术,人与动物签订契约,进行某种交换。这中间的沟通渠道不变,签订契约的人就可以随时随地进行通灵。一个类中,应该定义足够的安全的接口。然而在JS中,语言特性非常灵活,类中的公有方法和私有方法实际是一样的。因此,在实现类的时候,应该避免公开未定义于接口的方法。

创建对象

Javascript中,创建对象的基本模式有三种。
1. 直接创建 对象中的所有方法都是公有的,可以公开访问。
2. 使用下划线 在私有方法名称前加下划线,表示该方法私有。
3. 使用闭包 闭包可以创建真正意义上的私有成员,这些成员只能通过特定方法访问。

直接创建

所谓直接创建,就是按照传统的方式创建一个类,构造器是一个函数,属性和方法全部公开,比如:

var Fruit = function(color, weight) {
  this.color = color || "orange";
  this.weight = weight || 150;
}

Fruit.prototype.boom = function() {
  ...  
}

这种方法一般来说没什么问题,但是当其原型上的方法boom对自身的属性color或者weight有一定依赖,而构造时传入的参数不符合一定要求时就会出错。但如果构造时没有出错则所有方法应该能正常工作才是。

当然这个问题可以在构造对象时就对传入的参数进行验证,也不算太严重。然而另一个问题在于,即使能对参数进行验证,任何程序员还是能够随意修改属性的值。为了解决这个问题,可以设计一个数据的取值器和赋值器。

var Fruit = function(color, weight) {
  this.setColor(color);
  this.setWeight(weight);
}

Fruit.prototype = {
  checkColor: function(color) {
    ...
  },
  setColor: function(color) {
    ...
    this.color = color;
  },
  getColor: function() {
    return this.color;
  },
  ...
}

当程序员之间约定以提供的方法对属性值进行操作时,操作过程可以相对得到规范。但实际上属性仍然是公开的,可以被直接设置,这种方法并不能阻止这种行为。

使用下划线,区别私用成员

此种方法与前一种方法其实是一回事,只是在私用的方法和属性前加了下划线表示它是私用的。

var Fruit = function(color, weight) {
  this.setColor(color);
  this.setWeight(weight);
}

Fruit.prototype = {
  _checkColor: function(color) {
    ...
  },
  setColor: function(color) {
    ... // 此处对输入的数据进行验证,不能通过验证就抛出异常
    this._color = color;
  },
  getColor: function() {
    return this._color;
  },
  ...
}

这种方法有助于防止对私用方法的无意使用,但无法保证程序员不有意使用它们。

使用闭包

借助闭包就可以创建只允许特定函数访问的变量了,私用属性的创建方法即是在构造函数的作用域中创建变量即可,这些变量可以被该作用域中的所有函数访问。

var Fruit = function(newColor, weight) {
  var color, weight;

  // 私用方法
  function _checkColor = function(color) {
    ...
  }

  // 特权方法
  this.getColor = function() {
    return color;
  };

  this.setColor = function(newColor) {
    ... // 验证输入
    color = newColor;
  }

  // 构造过程代码
  this.setColor(newColor);
}

借助this关键字创建的方法就是特权方法了,它们是公开方法,同时也能够访问私有变量。如果需要创建一些不需要访问私有属性的方法的话,可以在Fruit.prototype上进行创建。通过这种方式创建的方法,不能直接访问私有变量,但是可以通过getColor这样的特权方法进行间接访问。

当然这种方式也有弊端,如果特权方法过多,会占用较多内存,因为通过构造函数创建的实例都保存了特权方法的副本,而原型上的方法只有一份。因此,在设计构造函数时,需要进行慎重考虑。

另一个问题就在于这样的方法无法作用于需要创建子类的场景,由于特权方法都是新的副本,所以子类无法访问超类的任何私用属性或方法。因此在Javascript中,这种问题被称作“继承破坏封装”(inheritance breaks encapsulation)。

高级创建对象模式初探

以上的三种方法属于创造对象的基本方法,但要实现一些高级的创建对象模式,有必要先了解一些概念。

静态方法和属性

前述的闭包创建对象法可以创建私用属性和方法,但是这样的话,这些属性在创建子类时会同时创建副本存于子类,造成内存的浪费。对于一些只需要在类层面进行操作和访问的属性,可以利用闭包创建静态成员。静态成员每个只有一份,直接通过类对象进行访问。

仍然以之前的Orange类为例,使用闭包特性添加一些静态成员:

var Orange = (function() {
  // 私用静态属性
  var numOfOranges = 0;

  // 私用静态方法
  function checkColor() {
    ...
  }

  // 返回构造器
  return function(newColor, weight) {
    var color, weight;

    // 特权方法
    this.getColor = function() {
      return color;
    };
    this.setColor = function(newColor) {
      ...
      color = newColor;
    }

    // 构造器代码
    numOfOrange++;
    if (numOfOrange > 100) {
      throw new Error("Only 100 instances of Orange can be created.");
    }

    this.setColor(newColor);
  }
})();

// 公开静态方法
Orange.turnToJuice = function() { // 不添加在Orange的prototype上
  ...
};

// 公开的非特权方法
Orange.prototype = {
  checkWeight: function() {
    ...
  }
};

这与之前的闭包创建类的最大区别在于构造器由一个普通函数变成了一个内嵌函数,通过一个自执行函数的返回值赋给Orange。在实例化Orange时,调用的是返回的内嵌函数,外层函数只是一个用来存放静态私用成员的闭包。在构造器中的特权方法可以访问构造器之外的静态属性和方法,而静态方法不能访问任何定义在构造器中的私用属性。

常量

常量就是不能被修改的变量,利用静态属性可以在Javascript中模拟常量。对常量只创建作为取值器的静态方法,而不创建赋值器,在外围作用域中也不能访问到常量:

var Class = (function() {
  // 常量
  var CONST = 100;

  // 构造器
  var constructorFunc = function(param) {
    ...
  };

  // 静态方法,取值器
  constructorFunc = function() {
    return CONST;
  };

  return constructorFunc;
})();
封装之利弊 封装之利

保护内部数据,只对外提供取值器和赋值器,便于重构。减少模块间耦合。

封装之弊

难以进行单元测试,外部测试无法访问到内部变量和方法。不过如公用方法可以间接访问私用方法的话,可以对私用方法进行间接单元测试。

实现过程较为复杂,调试难度比较大。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87512.html

相关文章

  • JavaScript设计模式与开发实践》读书笔记

    摘要:订阅模式的一个典型的应用就是后面会写一篇相关的读书笔记。享元模式享元模式的核心思想是对象复用,减少对象数量,减少内存开销。适配器模式对目标函数进行数据参数转化,使其符合目标函数所需要的格式。 设计模式 单例模式 JS的单例模式有别于传统面向对象语言的单例模式,js作为一门无类的语言。使用全局变量的模式来实现单例模式思想。js里面的单例又分为普通单例和惰性单例,惰性单例指的是只有这个实例...

    Panda 评论0 收藏0
  • Effective JavaScript读书笔记(一)

    摘要:如果为假值,不传或者传入,函数都会返回但是,传入这个值是完全有可能的,所以这种判断形势是不正确的或者使用来判断也可以原始类型优于封装类型对象拥有六个原始值基本类型布尔值,数字,字符串,,和对象。 作为一个前端新人,多读书读好书,夯实基础是十分重要的,正如盖楼房一样,底层稳固了,才能越垒越高。从开始学习到现在,基础的读了红宝书《JavaScript高级程序设计》,犀牛书《JavaScri...

    zhoutao 评论0 收藏0
  • javascript高级程序设计》第六章 读书笔记javascript继承6种方法

    摘要:继承的是超类型中构造函数中的属性,如上继承了属性,但没有继承原型中的方法。上述造成的结果是子类型实例中有两组超类型的构造函数中定义的属性,一组在子类型的实例中,一组在子类型实例的原型中。 ECMAScript只支持实现继承,主要依靠原型链来实现。与实现继承对应的是接口继承,由于script中函数没有签名,所以无法实现接口继承。 一、原型链 基本思想:利用原型让一个引用类型继承另一个引用...

    孙吉亮 评论0 收藏0
  • JavaScript 设计模式读书笔记(六)——门面模式

    摘要:简单的门面模式实例事件绑定函数门面模式的作用是将复杂的接口进行包装,变成一个便于使用的接口。还是以事件相关为例,事件绑定中还有两个常用的分别是和。 门面模式是什么,与其我去用笨拙的语言去解释,不如看下面这张图,曾经在网上很火的一张图片,说的是一位儿子为他的爸妈设置的电脑桌面。 showImg(http://segmentfault.com/img/bVcgHm); 有了这些起好名字...

    pubdreamcc 评论0 收藏0
  • JavaScript模式读书笔记()字面量和构造函数

    摘要:对象字面量定义一个空对象这里的空指的是其自身属性为空,对象继承了的属性和方法添加属性方法完全删除属性方法自定义构造函数用操作符调用构造函数时,函数内部会发发生以下情况创建一个新对象,并且引用了该对象并继承了该函数的原型属性和方法被加入到的引 对象字面量 //定义一个空对象,这里的空指的是其自身属性为空,dog对象继承了Object.prototype的属性和方法 var dog={} ...

    _Zhao 评论0 收藏0

发表评论

0条评论

lentrue

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<