设计模式的定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。
人类可以走到生物链顶端的前两个原因分别是会“使用名字”和“使用工具”。
所有设计模式都遵循一条原则,即“找出程序中变化的地方,并将变化封装起来”
API:(应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或某硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。
绝大部分设计模式的实现都离不开多态性的思想,所以在学习设计模式之前先来了解一下多态
1、静态语言中的多态:
//多态 //这种方法没有消除类型之间的耦合关系 /* 耦合:就是代码关系过于紧密,往往改一小段代码,需要整个项目做很大的改动。 * 在实际开发中应该尽量避免过高的耦合。接口、继承是解耦合的一种好方法。 */ class Duck { public void makeSound() { System.out.println("嘎嘎嘎"); } } class Chicken { public void makeSound() { System.out.println("咯咯咯"); } } class AnimalSound { public void makeSound(Duck duck) { duck.makeSound(); } // public void makeSound(Chicken chicken) { // chicken.makeSound(); // } } public class Test { public static void main(String[] args) { AnimalSound animalSound = new AnimalSound(); Duck duck = new Duck(); animalSound.makeSound(duck); // Chicken chicken = new Chicken(); // animalSound.makeSound(chicken); } }
2、静态语言中,继承实现多态效果:
// 继承实现多态效果 /* 多态的思想实际上是把“做什么”和“谁去做”分离开来,要实现这一点,归根结底先要消除类型之间的耦合关系。 * 如果类型之间的耦合关系没有被消除,那么我们在makeSound方法中指定了发出叫声的对象是某个类型,它就不可能再被替换成另外一个类型 * 在java中,可以通过向上转型来实现多态*/ abstract class Animal { abstract void makeSound(); //抽象方法 } class Duck extends Animal{ public void makeSound() { System.out.println("嘎嘎嘎"); } } class Chicken extends Animal{ public void makeSound() { System.out.println("咯咯咯"); } } class AnimalSound { public void makeSound(Animal animal) { //接受Animal类型的参数 animal.makeSound(); } } public class Test { public static void main(String[] args) { AnimalSound animalSound = new AnimalSound(); Duck duck = new Duck(); animalSound.makeSound(duck); Chicken chicken = new Chicken(); animalSound.makeSound(chicken); } }
3、JavaScript中的多态:
/*JavaScript的多态性是与生俱来的 JavaScript作为一门动态类型语言,它在编译时没有类型(包括对象类型和参数类型)检查的过程, 即不存在任何程度上的“类型耦合”,不需要向上转型*/ var googleMap = { show: function() { console.log('开始渲染谷歌地图'); } }; var baiduMap = { show: function() { console.log('开始渲染百度地图'); } }; // var sosoMap = { // show: function() { // console.log('开始渲染搜搜地图'); // } // }; var renderMap = function(type) { if (type=='googleMap') { googleMap.show(); }else if (type=='baiduMap') { baiduMap.show(); } // else if (type=='sosoMap') { // sosoMap.show(); // } }; renderMap('googleMap'); renderMap('baiduMap'); // renderMap('sosoMap');
var googleMap = { show: function() { console.log('开始渲染谷歌地图'); } }; var baiduMap = { show: function() { console.log('开始渲染百度地图'); } }; // var sosoMap = { // show: function() { // console.log('开始渲染搜搜地图'); // } // }; var renderMap = function(type) { if (type.show instanceof Function) { //Function必须大写,原因可以参考 //链接1:https://www.cnblogs.com/shuiyi/p/5343399.html //链接2:http://www.cnblogs.com/shuiyi/p/5305435.html //链接3:https://zhidao.baidu.com/question/134033299.html?qbl=relate_question_5&word=function%20%BA%CDfunction type.show(); } }; renderMap(googleMap); renderMap(baiduMap); // renderMap(sosoMap);
原型模式和基于原型继承的 JavaScript对象系统
1、使用克隆的原型模式
从设计模式的角度讲,原型模式是用于创建对象的一种模式,如果我们想要创建一个对象, 一种方法是先指定它的类型,然后通过类来创建这个对象。原型模式选择了另外一种方式,我们 不再关心对象的具体类型,而是找到一个对象,然后通过克隆来创建一个一模一样的对象。
原型模式的实现关键,是语言本身是否提供了clone方法。ECMAScript 5提供了Object.create 方法,可以用来克隆对象。代码如下:
var Plane = function() { this.blood = 100; this.attackLevel = 1; this.defenseLevel = 1; }; var plane = new Plane(); plane.blood = 500; plane.attackLevel = 10; plane.defenseLevel = 7; var clonePlane = Object.create(plane); //Object.create()方法可以用来克隆对象 // console.log(Plane); console.log(clonePlane); // 在不支持Object.create()的浏览器中,则可以使用以下代码: Object.create = Object.create || function(obj) { var F = function() {}; F.prototype = obj; return new F(); }
当然在 JavaScript这种类型模糊的语言中,创建对象非常容易,也不存在类型耦合的问题。 从设计模式的角度来讲,原型模式的意义并不算大 。但 JavaScript本身是一门基于原型的面向对 象语言,它的对象系统就是使用原型模式来搭建的,在这里称之为原型编程范型也许更合适。
2、体验 Io语言
事实上,使用原型模式来构造面向对象系统的语言远非仅有 JavaScript一家。 JavaScript 基于原型的面向对象系统参考了 Self 语言和 Smalltalk 语言,为了搞清 JavaScript 中的原型,我们本该寻根溯源去瞧瞧这两门语言。但由于这两门语言距离现在实在太遥远,我们 不妨转而了解一下另外一种轻巧又基于原型的语言——Io语言。 Io语言在 2002年由 Steve Dekorte发明。可以从http://iolanguage.com下载到 Io语言的解释器, 安装好之后打开 Io解释器,输入经典的“Hello World”程序。解释器打印出了 Hello World的字符串,这说明我们已经可以使用 Io语言来编写一些小程序了,如下图所示。
3、原型编程范型的一些规则
跟使用“类” 的语言不一样的地方是,Io语言中初只有一个根对象 Object,其他所有的对象都克隆自另外一 个对象。如果 A对象是从 B对象克隆而来的,那么 B对象就是 A对象的原型。
在上一小节的例子中,Object 是 Animal 的原型,而 Animal 是 Dog 的原型,它们之间形成了一 条原型链。这个原型链是很有用处的,当我们尝试调用 Dog 对象的某个方法时,而它本身却没有 这个方法,那么 Dog 对象会把这个请求委托给它的原型 Animal 对象,如果 Animal 对象也没有这个属性,那么请求会顺着原型链继续被委托给 Animal 对象的原型 Object 对象,这样一来便能得到继承的效果,看起来就像 Animal 是 Dog 的“父类”,Object 是 Animal 的“父类”。 这个机制并不复杂,却非常强大,Io和 JavaScript一样,基于原型链的委托机制就是原型继承的本质。
我们来进行一些测试。在 Io的解释器中执行 Dog makeSound 时,Dog 对象并没有 makeSound 方 法,于是把请求委托给了它的原型 Animal 对象 ,而 Animal 对象是有 makeSound 方法的,所以该条 语句可以顺利得到输出,如图 1-2所示。
我们可以发现原型编程范型至少包括以下基本规则。
所有的数据都是对象。
要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。
对象会记住它的原型。
如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。
4、JavaScript中的原型继承
我们不能说在 JavaScript中所有的数据都是对象,但可以说绝大部分数据都是对象。那么相 信在 JavaScript中也一定会有一个根对象存在,这些对象追根溯源都来源于这个根对象。 事实上,JavaScript中的根对象是 Object.prototype 对象。Object.prototype 对象是一个空的对象。我们在 JavaScript 遇到的每个对象,实际上都是从 Object.prototype 对象克隆而来的, Object.prototype 对象就是它们的原型。比如下面的 obj1 对象和 obj2 对象:
var obj1 = new Object(); var obj2 = {};
可以利用 ECMAScript 5提供的 Object.getPrototypeOf 来查看这两个对象的原型:
console.log( Object.getPrototypeOf( obj1 ) === Object.prototype ); // 输出:true
console.log( Object.getPrototypeOf( obj2 ) === Object.prototype ); // 输出:true
5、准确的来说:如果对象无法响应某个请求,它会把这个请求委托给它的构造器的原型
实际上,虽然 JavaScript 的对象最初都是由 Object.prototype 对象克隆而来的,但对象构造 器的原型并不仅限于 Object.prototype 上,而是可以动态指向其他对象。这样一来,当对象 a 需 要借用对象 b 的能力时,可以有选择性地把对象 a 的构造器的原型指向对象 b,从而达到继承的 效果。下面的代码是我们最常用的原型继承方式:
var obj = { name: 'sven' }; var A = function(){}; A.prototype = obj;
1、首先,尝试遍历对象 a 中的所有属性,但没有找到 name 这个属性。 2、查找 name 属性的这个请求被委托给对象 a 的构造器的原型,它被 a.__proto__ 记录着并且 指向 A.prototype,而 A.prototype 被设置为对象 obj。 3、在对象 obj 中找到了 name 属性,并返回它的值。
当我们期望得到一个“类”继承自另外一个“类”的效果时,往往会用下面的代码来模拟实现:
var A = function(){}; A.prototype = { name: 'sven' }; var B = function(){}; B.prototype = new A(); varb=newB(); console.log( b.name ); // 输出:sven
再看这段代码执行的时候,引擎做了什么事情。
1、首先,尝试遍历对象 b 中的所有属性,但没有找到 name 这个属性。 2、查找 name 属性的请求被委托给对象 b 的构造器的原型,它被 b.__proto__ 记录着并且指向 B.prototype,而 B.prototype 被设置为一个通过 new A()创建出来的对象。 3、在该对象中依然没有找到 name 属性,于是请求被继续委托给这个对象构造器的原型 A.prototype。 4、在 A.prototype 中找到了 name 属性,并返回它的值。
最后还要留意一点,原型链并不是无限长的。现在我们尝试访问对象 a 的 address 属性。而 对象 b 和它构造器的原型上都没有 address 属性,那么这个请求会被最终传递到哪里呢?
实际上,当请求达到 A.prototype,并且在 A.prototype 中也没有找到 address 属性的时候, 请求会被传递给 A.prototype 的构造器原型 Object.prototype,显然 Object.prototype 中也没有 address 属性,但 Object.prototype 的原型是 null,说明这时候原型链的后面已经没有别的节点了。 所以该次请求就到此打住,a.address 返回 undefined。
a.address // 输出:undefined