Jser 设计模式系列之面向对象 - 接口封装与继承
GOF在《设计模式》中说到:面向接口编程,而非面向实现编程
鉴于此,这个概念可见一斑!
JS却不像其他面向对象的高级语言(C#,Java,C++等)拥有内建的接口机制,以确定一组对象和另一组对象包含相似的的特性。所幸的是JS拥有强大的灵活性,这使得模仿接口特性又变得非常简单。那么到底是接口呢?
接口概念:
接口提供了一种用以说明一个对象应该具有那些方法的手段
接口,为一些具有相似行为的类之间(可能为同一种类型,也可能为不同类型)提供统一的方法定义,使这些类之间能够很好的实现通信
使用接口的优点:
- 自我描述性,促进代码的重用
- 明确一个类实现的方法,帮助其使用这个类
- 稳定不同类之间的通信
一个需求,需要多个部门协调合作的时候,接口的概念就特别重要了,每个部门可以按部就班的做自己的事情,涉及到交互的时候,提供接口处理即可
就好像主板上的内存条,CPU,硬盘,主板提供各种接口,其他设备直接按相应的接口插入即可
javascript语言要实现接口的概念还是有局限性的问题的
- 弱类型,具有极强的表现力的语言,那么使用了接口后,其实就强化了类型的作用,降低了语言的灵活性了
- 最重要的就是JS没有对这种机制的支持,没有强制性
javascript中模仿接口
常用的几种手法:
- 通过注释
- 通过属性检查模仿
- 鸭式辨型模仿
接口本身就是一个抽象的概念,至于如何实现各有不同
这是我的一段项目代码,接口是通过模块闭包实现的,这样的好处更能体现出封装性
//引入编译模块
define('ProcessMgr', [
'Compile'
], function(compile) { var flipContentProcessed,
flipWidgetProcessed, disposeWidgetBind, objectKeys,
converWidgetWapper, ActionMgr, getHotspot, //内部消息传递接口
//
// 1 构建节点树
// A 构建纯DOM节点
// B 构建content对象, 这是第二种加载模式,动态预加载
// 2 绑定节点事件
// 3 手动触发事件
// 4 自动触发事件
// 5 事件委托处理
// 6 翻页动作
// 7 翻页完成动作
// 8 复位动作
// 9 销毁动作
// 10 移除整个页面结构
//
ProcessMgr =
{
'preCompile' : preCompile,
'executeCompile' : executeCompile,
'assignTrigger' : assignTrigger,
'assignAutoRun' : assignAutoRun,
'assignSuspend' : assignSuspend,
'assignDispose' : assignDispose,
'recovery' : recovery,
'destroy' : destroy,
'removePage' : removePage
}, ...........具体处理的方法................... return ProcessMgr; //返回对外接口
})
jQuery的接口更加的直接,直接挂在在对应的原型链上或是静态链
封装
为什么要封装?
因为我们不关心它是如何实现的,我们只关心如何使用
手机,电脑,我们接触的形形色色的东西,其实我们一直都只是在使用它
同样的道理,我们放到程序设计中也是如此,封装实现的细节 ,降低对象之间的耦合程序,保持数据的完整性与修改的约束
所以说一个设计良好的API可以让开发者赏心悦目
javascript实现封装的手段
对于JS语法规则,我们要牢牢抓住3点
- JS函数是一等对象
- JS是函数级的作用域,意味着函数内部的变量不能被外部访问
- JS是词法性质的静态作用域,换句话说,即便在执行期作用域还是在定义的时候就预先分配好了
根据这3个规则我们就可以干很多别的语言干不了的事了
我们来模拟一个完整的封装
私有属性和方法
var encapsulation = function(){
//私有属性
var name = 'aaron'
//私有方法
var getName = function(){
alert(name)
}
} alert(name) //空
函数作用域实现变量与方法私有化,外边自然无法方法,当然这种完全没有实际意义了,我们要配合后面的处理
特权属性和方法
简单的说就能够让实例直接有权力访问内部的属性与方法,所以在设计上要修改下实现手法,
var encapsulation = function(){
//特权
this.name = 'aaron'
this.getName = function(){
alert(name)
}
}
alert(new encapsulation().name) //aaron
弊端也显而易见,每次new一个新的对象,特权属性与方法都会被重新拷贝一份,也就是需要单独占据一块堆内存
共有属性和方法
*注意了,命名函数与函数表达式在本质上虽然没什么区别,但是在处理上还是有很大不同
函数表达式
var encapsulation = function(){
//静态属性方法
encapsulation.name = 'aaron'
encapsulation.getName = function(){
alert(name)
}
} console.log( encapsulation.name ) // 无
命名函数
即便方法不执行,静态属性也能访问到,就证明了JS有一种预解析的机制,也就是常说的函数提升了
function encapsulation(){
//静态属性方法
encapsulation.name = 'aaron'
encapsulation.getName = function(){
alert(name)
}
} console.log( encapsulation.name ) // aaron
共有静态属性和方法
这是最最常用的,JS 的prototype原型特性,可以共享所有属性与方法,当然,属性是引用类型的时候就会存在问题了
var encapsulation = function(){
//共享属性方法
encapsulation.prototype.name = 'aaron'
encapsulation.prototype.getName = function(){
alert(name)
}
} console.log(new encapsulation().name ) // aaron
console.log(new encapsulation().name ) // aaron
继承
大型设计中,代码肯定是很多的,所以我们需要减少重复性的代码,尽可能的弱化对象之间的耦合:
通过几种手段
- 类式继承
- 原型式继承
- 掺元混入
当然并非所有的重复代码都可以使用一种继承方法,所以我们一般要区分共性是否一致
我项目中使用的继承手段
呵呵,眼熟吧,借鉴EXT的处理机制,有继承与混入,自己改了点,毕竟那种分层结构,倒序调用,还有点不合适套用,下文会介绍实现
类式继承
现在的框架,库大多继承的手段都是类式继承,而且继承的处理方式也基本一致,但是里面的细节问题可能大家没注意到,我们一起分析下
我们看看几个框架的继承实现
mootools


Classvar Class = this.Class = new Type('Class', function(params){
if (instanceOf(params, Function)) params = {initialize: params}; var newClass = function(){
reset(this);
if (newClass.$prototyping) return this;
this.$caller = null;
var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
this.$caller = this.caller = null;
return value;
}.extend(this).implement(params); newClass.$constructor = Class;
newClass.prototype.$constructor = newClass;
newClass.prototype.parent = parent; return newClass;
}); var parent = function(){
if (!this.$caller) throw new Error('The method "parent" cannot be called.');
var name = this.$caller.$name,
parent = this.$caller.$owner.parent,
previous = (parent) ? parent.prototype[name] : null;
if (!previous) throw new Error('The method "' + name + '" has no parent.');
return previous.apply(this, arguments);
};
Backbone


extend // Shared empty constructor function to aid in prototype-chain creation.
var ctor = function () {
}; // Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
var extend = function (protoProps, staticProps) {
var parent = this;
var child; // The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
child = function () {
parent.apply(this, arguments);
};
} // Inherit class (static) properties from parent.
_.extend(child, parent); // Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
ctor.prototype = parent.prototype;
child.prototype = new ctor(); // Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps); // Add static properties to the constructor function, if supplied.
if (staticProps) _.extend(child, staticProps); //执行完child.prototype=new ctor后,child.prototype.constructor已经不指向child,所以此处需要显示设置
child.prototype.constructor = child; // Set a convenience property in case the parent's prototype is needed later.
child.__super__ = parent.prototype; return child;
};
ext
extend /**
*
* 继承父类或者base顶层基类
*
*/
Class.registerPreprocessor('extend', function(cls, data, fn) { var extend = data.extend,
base = Xut.Base,
temp = function() {},
parent, i, k, ln, staticName, parentStatics; delete data.extend; //继承顶级base,默认继承父类
if (typeof extend === 'function' && extend !== Object) {
parent = extend;
} else {
parent = base;
} temp.prototype = parent.prototype;
cls.prototype = new temp(); if (!('$class' in parent)) {
for (i in base.prototype) {
if (!parent.prototype[i]) {
parent.prototype[i] = base.prototype[i];
}
}
} cls.prototype.self = cls; if (data.hasOwnProperty('constructor')) {
cls.prototype.constructor = cls;
} else {
cls.prototype.constructor = parent.prototype.constructor;
} cls.superclass = cls.prototype.superclass = parent.prototype; delete data.extend; fn.call(this, cls, data); });
ext比较特殊,因为是通过注入的手法,实现继承了类名转化继承混入静态扩充
对比几个框架,我们红线部分的相同之处没?实现原理确是一样的,包括EXT也一样,只是在具体实现上增加了各自的方案,
设计的原理
抛开复杂的框架,我们看看设计的底层原理是什么,又会有什么问题?
原型链作为JS实现继承的主要方法,其根本的思路利用原型链让一个引用类型,继承另一个引用类型
原型与实例的关系:
构造器有一个原型对象,原型对象有一个指针指向构造器,那么实例则是包含一个指向原型的指针
所以实例只与原型有关系
实现中的细节:
function SuperType(){ //父类
this.property = true;
} SuperType.prototype.getSuperValue = function(){
return this.property;
}; function SubType(){ //子类
this.subproperty = false;
} //继承了 SuperType
SubType.prototype = new SuperType(); //实现原型继承,引用 SubType.prototype.getSubValue = function (){
return this.subproperty;
}; var instance = new SubType(); alert(instance.getSuperValue()); //true
1 继承的本质是引用,那么N多组实例其实都是操作的同一个引用,那么问题来了,如果父类中有个一引用属性,那么一个子类操作修改了,所有的子类都会被影响
function SuperType(){
this.colors = ["red", "blue", "green"];
} function SubType(){
} //继承了 SuperType
SubType.prototype = new SuperType(); var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
2 在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数(constructor stealing)的技术(有时候也叫做伪造对象或经典继承)
function SuperType(){
this.colors = ["red", "blue", "green"];
} function SubType(){
//继承了 SuperType
SuperType.call(this);
} var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
通过使用 call()方法(或 apply()方法也可以),我们实际上是在(未来将要)新创建的 SubType 实例的环境下调用了 SuperType 构造函数。这样一来,就会在新 SubType 对象上执行 SuperType()函数中定义的所有对象初始化代码。结果,SubType 的每个实例就都会具有自己的 colors 属性的副本了
借用构造函数的问题
如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的
通过SubType.prototype = new SuperType(); 的方式实现引用的继承,看似很简单,但是里面还有几个问题
- 不管什么情况,都把父类的实例属性与方法都复制给了子类
- 如果子类修改了原型共享的引用属性,倒置所有继承的会受引向
- 借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了
看看Backbone的如何处理
var ctor = function () { };
ctor.prototype = parent.prototype; child.prototype = new ctor(); child.prototype.constructor = child;
可见backbone引用一个中介ctor函数
其实就是一种代理继承
proxy来实现继承,这样就避免了在classical继承中出现的parent的constructor被使用两次带来的效率问题和在原型链中再次继承this的属性
function obj (o){
var f = {}
f.prototype = o
return new f();
}
最后还要修正一下constructor的指针,因为是继承ctor了
至于原型式继承可以参考高级程序设计3, 比较详细了
Jser 设计模式系列之面向对象 - 接口封装与继承的更多相关文章
- python面向对象编程 -- 封装、继承
面向对象编程 -- 封装.继承 面向对象编程三要素:封装.继承和多态.本文主要看和封装.继承相关的概念:在python中多态的概念比较模糊,本文不做讨论. 1 封装 封装:将数据和操作组装到一起,对外 ...
- 3、C#面向对象:封装、继承、多态、String、集合、文件(下)
面向对象多态 一.装箱和拆箱 装箱:将值类型转换为引用类型.object o = 1:值类型给引用类型赋值 拆箱:将引用类型转换为值类型.int n = (int)o; 强制转换为值类型 满足条件:两 ...
- Java学习之旅基础知识篇:面向对象之封装、继承及多态
Java是一种面向对象设计的高级语言,支持继承.封装和多态三大基本特征,首先我们从面向对象两大概念:类和对象(也称为实例)谈起.来看看最基本的类定义语法: /*命名规则: *类名(首字母大写,多个单词 ...
- [js高手之路]设计模式系列课程-组合模式+寄生组合继承实战新闻列表
所谓组合模式,就是把一堆结构分解出来,组成在一起,现实中很多这样的例子,如: 1.肯德基套餐就是一种组合模式, 比如鸡腿堡套餐,一般是是由一个鸡腿堡,一包薯条,一杯可乐等组成的 2.组装的台式机同理, ...
- JavaScript 定义类的最佳写法——完整支持面向对象(封装、继承、多态),兼容所有浏览器,支持用JSDuck生成文档
作者: zyl910 [TOC] 一.缘由 由于在ES6之前,JavaScript中没有定义类(class)语法.导致大家用各种五花八门的办法来定义类,代码风格不统一.而且对于模拟面向对象的三大支柱& ...
- python面向对象(封装、继承、多态)+ 面向对象小栗子
大家好,下面我说一下我对面向对象的理解,不会讲的很详细,因为有很多人的博客都把他写的很详细了,所以,我尽可能简单的通过一些代码让初学者可以理解面向对象及他的三个要素. 摘要:1.首先介绍一下面向对象 ...
- Java 面向对象,封装,继承
1相关概念的理解 1.1面向过程.面向对象 面向过程与面向对象都是编程中,编写程序的一种思维方式. 面向过程的程序设计方式,是遇到一件事时,思考“我该怎么做”,然后一步步实现的过程.(职员思想) 面向 ...
- AJPFX关于面向对象之封装,继承,多态 (下)
(3)private: 对于对于成员来说:只能在该成员隶属于的类中访问. 对于类来说:类不可以声明为private. 4)protected: 对于对于成员来说:相同包中的类可以访问(包访问权限):基 ...
- 2、C#面向对象:封装、继承、多态、String、集合、文件(上)
面向对象封装 一.面向对象概念 面向过程:面向的是完成一件事情的过程,强调的是完成这件事情的动作. 面向对象:找个对象帮你完成这件事情. 二.面向对象封装 把方法进行封装,隐藏实现细节,外部直接调用. ...
随机推荐
- BAT 技术团队博客
1. 美团技术团队博客: 地址: http://tech.meituan.com/ 2. 腾讯社交用户体验设计(ISUX) 地址:http://isux.tencent.com/ 3. 京东设计中心 ...
- linux安装oracle11g
准备oracle安装文件 Oracle11gR2包含两个文件linux_11gR2_database_1of2.zip和linux_11gR2_database_2of2.zip,将这两个文件通过SS ...
- Git使用出错:Couldn‘t reserve space for cygwin‘s heap, Win32
今天使用Git在命令行下更新代码遇到了问题,起初觉得是自己安装某软件导致冲突,从网上搜索了一下找到类似问题,成功解决问题. 错误信息如下: E:\storm-sql>git pull origi ...
- 设置label上文字大小、颜色不一样 NSMutableAttributedString的使用
- heml设置浏览器版本
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> action类获取se ...
- STM32之输入捕获以及小小应用(库)
五一之际,先祝大家五一快乐.其实快乐很简单,工作的人有假放,学习的人也有假放,像我,有假放才有更多的时间学自己想学的东西.51假期学51,可惜没有32假期呀.好了..言归正传,大家听过吸星大法吧..在 ...
- HTML5新特性——HTML 5 Canvas vs. SVG
Canvas 和 SVG 都允许您在浏览器中创建图形,但是它们在根本上是不同的. SVG SVG 是一种使用 XML 描述 2D 图形的语言. SVG 基于 XML,这意味着 SVG DOM 中的每个 ...
- 用SQL Server(T-SQL)获取连接字符串
一般情况下,C# 连接SQL Server的字符串可以直接按照说明文档直接手动写出来,或者也可以参考大名鼎鼎的connectionstrings手动拼写 但是如果你已经连接到SQL Server也可以 ...
- [LintCode] Sort List 链表排序
Sort a linked list in O(n log n) time using constant space complexity. Have you met this question in ...
- linux开启FTP以及添加用户配置权限,只允许访问自身目录,不能跳转根目录
1.切换到root用户 2.查看是否安装vsftp,我这个是已经安装的. [root@localhost vsftpd]# rpm -qa |grep vsftpd vsftpd--.el7_2.x8 ...