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 = oreturn new f(); }
最后还要修正一下constructor的指针,因为是继承ctor了
至于原型式继承可以参考高级程序设计3, 比较详细了
Jser 设计模式系列之面向对象 - 接口封装与继承相关推荐
- python面向对象编程(封装与继承)
一. 面向过程编程语言 "面向过程"(Procedure Oriented)是一种以过程为中心的编程思想.分析出解决问题所需要的步 骤,然后用函数把这些步骤一步一步实现,使用的时候 ...
- 如何理解面向对象的封装、继承、多态
如何理解面向对象的封装.继承.多态 面向对象可以说是一种对现实是事物的抽象,将一类事物抽象成一个类,类里面包含了这类事物具有的公共部分,以及我们对这些部分的操作,也就是对应的数据和过程. 面向对象思想 ...
- W6_面向对象_封装_继承_多继承_多态
W6_面向对象_封装_继承_多继承_多态 80.81.82.83.第02章节-Python3.5-面向对象介绍 84.第05章节-Python3.5-实例变量与类变量 85.第06章节-Python3 ...
- python面向对象编程 -- 封装、继承
面向对象编程 -- 封装.继承 面向对象编程三要素:封装.继承和多态.本文主要看和封装.继承相关的概念:在python中多态的概念比较模糊,本文不做讨论. 1 封装 封装:将数据和操作组装到一起,对外 ...
- Java SE(六)之面向对象(封装,继承,多态,接口)
文章目录 类和对象 1. 创建一个类 2. 创建一个对象 3. 访问变量和方法 4. some tips 封装 修饰符 1. 访问控制修饰符 2. 非访问修饰符 (1)static (2)final ...
- Go 学习笔记(36)— 基于Go方法的面向对象(封装、继承、多态)
Go 面向对象编程的三大特性:封装.继承和多态. 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式 继承:使得子类具有父类的属性和方法或者重新定义.追加属性和方法等 多态:不同对象中同种行为的不 ...
- 3、C#面向对象:封装、继承、多态、String、集合、文件(下)
面向对象多态 一.装箱和拆箱 装箱:将值类型转换为引用类型.object o = 1:值类型给引用类型赋值 拆箱:将引用类型转换为值类型.int n = (int)o; 强制转换为值类型 满足条件:两 ...
- extend implements多个对象_「每天三分钟跟我学Java」之Java面向对象的封装、继承、多态...
Java是面向对象的语言,深入理解面向对象的概念,对Java的开发至关重要.本节我们着重看下面向对象的三大特性,封装.继承.多态. 一.封装 封装是将类的某些信息隐藏在类内部,不允许外部程序直接访问, ...
- java程序员从笨鸟到菜鸟之_Java程序员从笨鸟到菜鸟之(二)面向对象之封装,继承,多态(上)...
Java是一种面向对象的语言,这是大家都知道的,他与那些像c语言等面向过程语言不同的是它本身所具有的面向对象的特性--封装,继承,多态,这也就是传说中的面向对象三大特性 一:从类和对象开始说起: Oo ...
最新文章
- 1055 The World‘s Richest
- python 类变量、实例变量、参数、实例方法、类方法、静态方法 的用法和区别
- 动态为程序指定快捷键
- tensorflow教程 开始——数据集:快速了解 tf.data
- ORACLE RAC 中 SRVCTL 命令详细说明
- 计算机体系结构 -- 第一章3 -- 设计的定量4个原则
- 域名是如何被墙的_如何快速搭建属于自己的个性网站?
- php 打乱数组顺序_PHP实现大转盘抽奖算法
- ROS调用ORB-SLAM2
- Windows系统安装adb/fastboot驱动教程
- ubuntu20.04.1下安装qt4相关依赖库
- 浙江概况——经济发展篇
- java网络文章博客抓取系统_java 后端博客系统文章系统——No5
- 如何在苹果iPhone或iPad上启用SSL证书
- 【kafka异常】使用Spring-kafka遇到的坑
- vue实现音乐平台项目
- matlab 区域生长算法生成二值图像边界区域
- 中国计算机技术职业资格网(软考)考试用书(大纲、教程、辅导用书)
- json解析格式化工具
- Java实现拼图小游戏(7)—— 计步功能及菜单业务的实现