GOF在《设计模式》中说到:面向接口编程,而非面向实现编程

鉴于此,这个概念可见一斑!

JS却不像其他面向对象的高级语言(C#,Java,C++等)拥有内建的接口机制,以确定一组对象和另一组对象包含相似的的特性。所幸的是JS拥有强大的灵活性,这使得模仿接口特性又变得非常简单。那么到底是接口呢?

接口概念:

接口提供了一种用以说明一个对象应该具有那些方法的手段

接口,为一些具有相似行为的类之间(可能为同一种类型,也可能为不同类型)提供统一的方法定义,使这些类之间能够很好的实现通信

使用接口的优点:

  • 自我描述性,促进代码的重用
  • 明确一个类实现的方法,帮助其使用这个类
  • 稳定不同类之间的通信

一个需求,需要多个部门协调合作的时候,接口的概念就特别重要了,每个部门可以按部就班的做自己的事情,涉及到交互的时候,提供接口处理即可

就好像主板上的内存条,CPU,硬盘,主板提供各种接口,其他设备直接按相应的接口插入即可

javascript语言要实现接口的概念还是有局限性的问题的

  • 弱类型,具有极强的表现力的语言,那么使用了接口后,其实就强化了类型的作用,降低了语言的灵活性了
  • 最重要的就是JS没有对这种机制的支持,没有强制性

javascript中模仿接口

常用的几种手法:

  • 通过注释
  • 通过属性检查模仿
  • 鸭式辨型模仿

接口本身就是一个抽象的概念,至于如何实现各有不同

这是我的一段项目代码,接口是通过模块闭包实现的,这样的好处更能体现出封装性

  1. //引入编译模块
  2. define('ProcessMgr', [
  3. 'Compile'
  4. ], function(compile) {
  5.  
  6. var flipContentProcessed,
  7. flipWidgetProcessed, disposeWidgetBind, objectKeys,
  8. converWidgetWapper, ActionMgr, getHotspot,
  9.  
  10. //内部消息传递接口
  11. //
  12. // 1 构建节点树
  13. // A 构建纯DOM节点
  14. // B 构建content对象, 这是第二种加载模式,动态预加载
  15. // 2 绑定节点事件
  16. // 3 手动触发事件
  17. // 4 自动触发事件
  18. // 5 事件委托处理
  19. // 6 翻页动作
  20. // 7 翻页完成动作
  21. // 8 复位动作
  22. // 9 销毁动作
  23. // 10 移除整个页面结构
  24. //

ProcessMgr =

  1. {
  2. 'preCompile' : preCompile,
  3. 'executeCompile' : executeCompile,
  4. 'assignTrigger' : assignTrigger,
  5. 'assignAutoRun' : assignAutoRun,
  6. 'assignSuspend' : assignSuspend,
  7. 'assignDispose' : assignDispose,
  8. 'recovery' : recovery,
  9. 'destroy' : destroy,
  10. 'removePage' : removePage
  11. },
  12.  
  13. ...........具体处理的方法...................
  14.  
  15. return ProcessMgr; //返回对外接口
  16. })

jQuery的接口更加的直接,直接挂在在对应的原型链上或是静态链


封装

为什么要封装?

因为我们不关心它是如何实现的,我们只关心如何使用

手机,电脑,我们接触的形形色色的东西,其实我们一直都只是在使用它

同样的道理,我们放到程序设计中也是如此,封装实现的细节 ,降低对象之间的耦合程序,保持数据的完整性与修改的约束

所以说一个设计良好的API可以让开发者赏心悦目

javascript实现封装的手段

对于JS语法规则,我们要牢牢抓住3点

  • JS函数是一等对象
  • JS是函数级的作用域,意味着函数内部的变量不能被外部访问
  • JS是词法性质的静态作用域,换句话说,即便在执行期作用域还是在定义的时候就预先分配好了

根据这3个规则我们就可以干很多别的语言干不了的事了

我们来模拟一个完整的封装

私有属性和方法

  1. var encapsulation = function(){
  2. //私有属性
  3. var name = 'aaron'
  4. //私有方法
  5. var getName = function(){
  6. alert(name)
  7. }
  8. }
  9.  
  10. alert(name) //空

函数作用域实现变量与方法私有化,外边自然无法方法,当然这种完全没有实际意义了,我们要配合后面的处理

特权属性和方法

简单的说就能够让实例直接有权力访问内部的属性与方法,所以在设计上要修改下实现手法,

  1. var encapsulation = function(){
  2. //特权
  3. this.name = 'aaron'
  4. this.getName = function(){
  5. alert(name)
  6. }
  7. }
  8. alert(new encapsulation().name) //aaron

弊端也显而易见,每次new一个新的对象,特权属性与方法都会被重新拷贝一份,也就是需要单独占据一块堆内存

共有属性和方法

*注意了,命名函数与函数表达式在本质上虽然没什么区别,但是在处理上还是有很大不同

函数表达式

  1. var encapsulation = function(){
  2. //静态属性方法
  3. encapsulation.name = 'aaron'
  4. encapsulation.getName = function(){
  5. alert(name)
  6. }
  7. }
  8.  
  9. console.log( encapsulation.name ) // 无

命名函数

即便方法不执行,静态属性也能访问到,就证明了JS有一种预解析的机制,也就是常说的函数提升了

  1. function encapsulation(){
  2. //静态属性方法
  3. encapsulation.name = 'aaron'
  4. encapsulation.getName = function(){
  5. alert(name)
  6. }
  7. }
  8.  
  9. console.log( encapsulation.name ) // aaron

共有静态属性和方法

这是最最常用的,JS 的prototype原型特性,可以共享所有属性与方法,当然,属性是引用类型的时候就会存在问题了

  1. var encapsulation = function(){
  2. //共享属性方法
  3. encapsulation.prototype.name = 'aaron'
  4. encapsulation.prototype.getName = function(){
  5. alert(name)
  6. }
  7. }
  8.  
  9. console.log(new encapsulation().name ) // aaron
  10. console.log(new encapsulation().name ) // aaron

继承

大型设计中,代码肯定是很多的,所以我们需要减少重复性的代码,尽可能的弱化对象之间的耦合:

通过几种手段

  • 类式继承
  • 原型式继承
  • 掺元混入

当然并非所有的重复代码都可以使用一种继承方法,所以我们一般要区分共性是否一致

我项目中使用的继承手段

呵呵,眼熟吧,借鉴EXT的处理机制,有继承与混入,自己改了点,毕竟那种分层结构,倒序调用,还有点不合适套用,下文会介绍实现

类式继承

现在的框架,库大多继承的手段都是类式继承,而且继承的处理方式也基本一致,但是里面的细节问题可能大家没注意到,我们一起分析下

我们看看几个框架的继承实现

mootools

  1. Classvar Class = this.Class = new Type('Class', function(params){
  2. if (instanceOf(params, Function)) params = {initialize: params};
  3.  
  4. var newClass = function(){
  5. reset(this);
  6. if (newClass.$prototyping) return this;
  7. this.$caller = null;
  8. var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
  9. this.$caller = this.caller = null;
  10. return value;
  11. }.extend(this).implement(params);
  12.  
  13. newClass.$constructor = Class;
  14. newClass.prototype.$constructor = newClass;
  15. newClass.prototype.parent = parent;
  16.  
  17. return newClass;
  18. });
  19.  
  20. var parent = function(){
  21. if (!this.$caller) throw new Error('The method "parent" cannot be called.');
  22. var name = this.$caller.$name,
  23. parent = this.$caller.$owner.parent,
  24. previous = (parent) ? parent.prototype[name] : null;
  25. if (!previous) throw new Error('The method "' + name + '" has no parent.');
  26. return previous.apply(this, arguments);
  27. };

Backbone

  1. extend // Shared empty constructor function to aid in prototype-chain creation.
  2. var ctor = function () {
  3. };
  4.  
  5. // Helper function to correctly set up the prototype chain, for subclasses.
  6. // Similar to `goog.inherits`, but uses a hash of prototype properties and
  7. // class properties to be extended.
  8. var extend = function (protoProps, staticProps) {
  9. var parent = this;
  10. var child;
  11.  
  12. // The constructor function for the new subclass is either defined by you
  13. // (the "constructor" property in your `extend` definition), or defaulted
  14. // by us to simply call the parent's constructor.
  15. if (protoProps && protoProps.hasOwnProperty('constructor')) {
  16. child = protoProps.constructor;
  17. } else {
  18. child = function () {
  19. parent.apply(this, arguments);
  20. };
  21. }
  22.  
  23. // Inherit class (static) properties from parent.
  24. _.extend(child, parent);
  25.  
  26. // Set the prototype chain to inherit from `parent`, without calling
  27. // `parent`'s constructor function.
  28. ctor.prototype = parent.prototype;
  29. child.prototype = new ctor();
  30.  
  31. // Add prototype properties (instance properties) to the subclass,
  32. // if supplied.
  33. if (protoProps) _.extend(child.prototype, protoProps);
  34.  
  35. // Add static properties to the constructor function, if supplied.
  36. if (staticProps) _.extend(child, staticProps);
  37.  
  38. //执行完child.prototype=new ctor后,child.prototype.constructor已经不指向child,所以此处需要显示设置
  39. child.prototype.constructor = child;
  40.  
  41. // Set a convenience property in case the parent's prototype is needed later.
  42. child.__super__ = parent.prototype;
  43.  
  44. return child;
  45. };

ext

  1. extend /**
  2. *
  3. * 继承父类或者base顶层基类
  4. *
  5. */
  6. Class.registerPreprocessor('extend', function(cls, data, fn) {
  7.  
  8. var extend = data.extend,
  9. base = Xut.Base,
  10. temp = function() {},
  11. parent, i, k, ln, staticName, parentStatics;
  12.  
  13. delete data.extend;
  14.  
  15. //继承顶级base,默认继承父类
  16. if (typeof extend === 'function' && extend !== Object) {
  17. parent = extend;
  18. } else {
  19. parent = base;
  20. }
  21.  
  22. temp.prototype = parent.prototype;
  23. cls.prototype = new temp();
  24.  
  25. if (!('$class' in parent)) {
  26. for (i in base.prototype) {
  27. if (!parent.prototype[i]) {
  28. parent.prototype[i] = base.prototype[i];
  29. }
  30. }
  31. }
  32.  
  33. cls.prototype.self = cls;
  34.  
  35. if (data.hasOwnProperty('constructor')) {
  36. cls.prototype.constructor = cls;
  37. } else {
  38. cls.prototype.constructor = parent.prototype.constructor;
  39. }
  40.  
  41. cls.superclass = cls.prototype.superclass = parent.prototype;
  42.  
  43. delete data.extend;
  44.  
  45. fn.call(this, cls, data);
  46.  
  47. });

ext比较特殊,因为是通过注入的手法,实现继承了类名转化继承混入静态扩充

对比几个框架,我们红线部分的相同之处没?实现原理确是一样的,包括EXT也一样,只是在具体实现上增加了各自的方案,


设计的原理

抛开复杂的框架,我们看看设计的底层原理是什么,又会有什么问题?

原型链作为JS实现继承的主要方法,其根本的思路利用原型链让一个引用类型,继承另一个引用类型

原型与实例的关系:

构造器有一个原型对象,原型对象有一个指针指向构造器,那么实例则是包含一个指向原型的指针

所以实例只与原型有关系

  1.  
  1.  
  1. 实现中的细节:
 
  1. function SuperType(){ //父类
  2. this.property = true;
  3. }
  4.  
  5. SuperType.prototype.getSuperValue = function(){
  6. return this.property;
  7. };
  8.  
  9. function SubType(){ //子类
  10. this.subproperty = false;
  11. }
  12.  
  13. //继承了 SuperType
  14. SubType.prototype = new SuperType(); //实现原型继承,引用
  15.  
  16. SubType.prototype.getSubValue = function (){
  17. return this.subproperty;
  18. };
  19.  
  20. var instance = new SubType();
  21.  
  22. alert(instance.getSuperValue()); //true
  1.  
  1.  
  1.  
  1. 1 继承的本质是引用,那么N多组实例其实都是操作的同一个引用,那么问题来了,如果父类中有个一引用属性,那么一个子类操作修改了,所有的子类都会被影响
  1.  
  1. function SuperType(){
  2. this.colors = ["red", "blue", "green"];
  3. }
  4.  
  5. function SubType(){
  6. }
  7.  
  8. //继承了 SuperType
  9. SubType.prototype = new SuperType();
  10.  
  11. var instance1 = new SubType();
  12. instance1.colors.push("black");
  13. alert(instance1.colors); //"red,blue,green,black"
  14.  
  15. var instance2 = new SubType();
  16. alert(instance2.colors); //"red,blue,green,black"
  1.  
  1.  
  1. 2 在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数(constructor stealing)的技术(有时候也叫做伪造对象或经典继承)
  1.  
  1. function SuperType(){
  2. this.colors = ["red", "blue", "green"];
  3. }
  4.  
  5. function SubType(){
  6. //继承了 SuperType
  7. SuperType.call(this);
  8. }
  9.  
  10. var instance1 = new SubType();
  11. instance1.colors.push("black");
  12. alert(instance1.colors); //"red,blue,green,black"
  13.  
  14. var instance2 = new SubType();
  15. alert(instance2.colors); //"red,blue,green"

通过使用 call()方法(或 apply()方法也可以),我们实际上是在(未来将要)新创建的 SubType 实例的环境下调用了 SuperType 构造函数。这样一来,就会在新 SubType 对象上执行 SuperType()函数中定义的所有对象初始化代码。结果,SubType 的每个实例就都会具有自己的 colors 属性的副本了

借用构造函数的问题
如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的

通过SubType.prototype = new SuperType(); 的方式实现引用的继承,看似很简单,但是里面还有几个问题

  • 不管什么情况,都把父类的实例属性与方法都复制给了子类
  • 如果子类修改了原型共享的引用属性,倒置所有继承的会受引向
  • 借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了

看看Backbone的如何处理

  1. var ctor = function () {
  2.  
  3. };
  1. ctor.prototype = parent.prototype;
  2.  
  3. child.prototype = new ctor();
  4.  
  5. child.prototype.constructor = child;

可见backbone引用一个中介ctor函数

其实就是一种代理继承

  1. proxy来实现继承,这样就避免了在classical继承中出现的parentconstructor被使用两次带来的效率问题和在原型链中再次继承this的属性
  1. function obj (o){
  2. var f = {}
  3. f.prototype = o
  4. return new f();
  5. }

最后还要修正一下constructor的指针,因为是继承ctor了

至于原型式继承可以参考高级程序设计3, 比较详细了

Jser 设计模式系列之面向对象 - 接口封装与继承的更多相关文章

  1. python面向对象编程 -- 封装、继承

    面向对象编程 -- 封装.继承 面向对象编程三要素:封装.继承和多态.本文主要看和封装.继承相关的概念:在python中多态的概念比较模糊,本文不做讨论. 1 封装 封装:将数据和操作组装到一起,对外 ...

  2. 3、C#面向对象:封装、继承、多态、String、集合、文件(下)

    面向对象多态 一.装箱和拆箱 装箱:将值类型转换为引用类型.object o = 1:值类型给引用类型赋值 拆箱:将引用类型转换为值类型.int n = (int)o; 强制转换为值类型 满足条件:两 ...

  3. Java学习之旅基础知识篇:面向对象之封装、继承及多态

    Java是一种面向对象设计的高级语言,支持继承.封装和多态三大基本特征,首先我们从面向对象两大概念:类和对象(也称为实例)谈起.来看看最基本的类定义语法: /*命名规则: *类名(首字母大写,多个单词 ...

  4. [js高手之路]设计模式系列课程-组合模式+寄生组合继承实战新闻列表

    所谓组合模式,就是把一堆结构分解出来,组成在一起,现实中很多这样的例子,如: 1.肯德基套餐就是一种组合模式, 比如鸡腿堡套餐,一般是是由一个鸡腿堡,一包薯条,一杯可乐等组成的 2.组装的台式机同理, ...

  5. JavaScript 定义类的最佳写法——完整支持面向对象(封装、继承、多态),兼容所有浏览器,支持用JSDuck生成文档

    作者: zyl910 [TOC] 一.缘由 由于在ES6之前,JavaScript中没有定义类(class)语法.导致大家用各种五花八门的办法来定义类,代码风格不统一.而且对于模拟面向对象的三大支柱& ...

  6. python面向对象(封装、继承、多态)+ 面向对象小栗子

    大家好,下面我说一下我对面向对象的理解,不会讲的很详细,因为有很多人的博客都把他写的很详细了,所以,我尽可能简单的通过一些代码让初学者可以理解面向对象及他的三个要素. 摘要:1.首先介绍一下面向对象 ...

  7. Java 面向对象,封装,继承

    1相关概念的理解 1.1面向过程.面向对象 面向过程与面向对象都是编程中,编写程序的一种思维方式. 面向过程的程序设计方式,是遇到一件事时,思考“我该怎么做”,然后一步步实现的过程.(职员思想) 面向 ...

  8. AJPFX关于面向对象之封装,继承,多态 (下)

    (3)private: 对于对于成员来说:只能在该成员隶属于的类中访问. 对于类来说:类不可以声明为private. 4)protected: 对于对于成员来说:相同包中的类可以访问(包访问权限):基 ...

  9. 2、C#面向对象:封装、继承、多态、String、集合、文件(上)

    面向对象封装 一.面向对象概念 面向过程:面向的是完成一件事情的过程,强调的是完成这件事情的动作. 面向对象:找个对象帮你完成这件事情. 二.面向对象封装 把方法进行封装,隐藏实现细节,外部直接调用. ...

随机推荐

  1. Linux添加开机启动命令

    1.vi /etc/rc.d/rc.local 添加要启动的命令 如: service php-fpm start  //这样,开机就自动启动了php扩展 2. crontab -e   //是写定时 ...

  2. javascript常识

    substring和substr的区别 substring() 方法用于提取字符串中介于两个指定下标之间的字符. stringObject.substring(start,stop)例子: <s ...

  3. Cordova 3.x入门 - 目录

    这个系列是基于Cordova 3.x的,很多Android的东西都是Eclipse ADT+Ant的,而目前Android的开发已经完全切换到了Android Studio+Gradle,需要大家特别 ...

  4. Leetcode Power of Two

    Given an integer, write a function to determine if it is a power of two. 题目意思: 给定一个整数,判断是否是2的幂 解题思路: ...

  5. Easyui之datagrid实现点击单元格修改单元格背景颜色

    前段时间有个需求中有点击datagrid的单元格实现某种事件,调用datagrid的onclickCell这个方法很容易实现,但是体验不好啊,完全不知道自己刚才点击的是哪个单元格,然后就尝试单击单元格 ...

  6. 关于Web服务器的认识

    马上就要毕业了,也要开始找工作了,大学写了这么多代码了,却没有好好总结一下常用的概念很是遗憾额,就通过这篇博客记录一下我最常用的一些知识好了. 说到Web服务器,有很多文章都介绍的很好,之前看到一篇非 ...

  7. Python2 基本数据结构源码解析

    Python2 基本数据结构源码解析 Contents 0x00. Preface 0x01. PyObject 0x01. PyIntObject 0x02. PyFloatObject 0x04. ...

  8. Akka-actor使用入门

    学习scala编程,不可避免的会接触到actor模式,它使得并发编程不再像噩梦般萦绕着开发者,Akka是actor的一个开源实现.由于本人水平有限,自认为还不能把actor设计思想讲明白,所以本文仅仅 ...

  9. HTML 上传图片实用小技巧

    最近写的项目需要用的上传图片的功能但是浏览器自带的按钮样式实在是不忍直视,肯定要进行修改,网上也有很多方法(自己查....),我这里用了个取巧的方法:就是函数的间接调用 在点击btn的时候让它执行了图 ...

  10. TLV(类型—长度—值)格式及编码

    转自: http://www.cnblogs.com/tml839720759/archive/2014/07/13/3841820.html 引子: 前段时间在项目中第一次接触TLV,项目中用这种格 ...