学习设计模式前需要知道的事情

来源:http://www.chinese-glasses.com 作者:Web前端 人气:67 发布时间:2020-03-24
摘要:时间: 2019-11-04阅读: 132标签: 封装前言 为什么要学习设计模式? 做事情之前问个为什么总是好的。关于设计模式的好坏,我在知乎上也看过一些讨论,有知友对其提出过一些疑问,里面

时间: 2019-11-04阅读: 132标签: 封装前言

为什么要学习设计模式?

做事情之前问个为什么总是好的。关于设计模式的好坏,我在知乎上也看过一些讨论,有知友对其提出过一些疑问,里面有一些关于设计模式的观点:

设计模式有何不妥,所谓的荼毒体现在哪?

设计模式是不是有点太玄了?

任何事物的出现都有其道理,任何语言都有其不足之处,设计模式是对语言不足的补充(Peter Norvig)。设计模式也是编程经验的总结,我想学习它对像我这样的前端新手的能力会有很大的提升。

JS是一种基于对象的语言,在JS中几乎所有的东西都可以看成是一个对象,但是JS中的对象模型和大多数面向对象语言的对象模型不太一样,因此理解JS中面向对象思想十分重要,接下来本篇文章将从多态、封装、继承三个基本特征来理解JS的面向对象思想

使用设计模式的好处

《Head First 设计模式》一书举了一个非常有意思的例子,

两个人点餐,一个人说:

"我要一份涂了奶酪及果酱的白面包,加了香草冰淇淋的巧克力汽水..";

另外一个人说:

“给我一份C.J怀特,一个黑与白”

表达同样的意思,效果却完全不同,这里就说到一个比较大的概念“共享词汇”

共享词汇就是一组概念,行为,方法等等的集合,这个在任何行业都有用,就像中文菜名“回锅肉”,厨子知道什么是回锅肉,你也知道什么是回锅肉,如果没有回锅肉这个共享词汇,我好像真的想不到一个合适的描述来点这道菜。

回到设计模式,各种设计模式就是一个个的共享词汇,它不仅仅是一个名称,更是一整套模式背后所象征的质量,特征,约束

细说说它的好处:

  • 设计模式能让你用更少的词汇做更充分的沟通;
  • 谈话在模式层次时,不会被压低到对象和类这种琐碎的事情上;
  • 懂设计模式的团队,彼此之间对于设计的看法不容易产生误解;
  • 共享词汇能帮助初级人员快速成长。

总结一下设计模式的作用:

帮助我们将应用组织成容易了解,容易维护,具有弹性的架构,建立可维护的OO系统,要诀在于随时想到系统以后可能需要的变化以及应付变化的原则。

多态含义

设计模式的使用方法

关于使用方式,像我这种初学者最容易犯的错误就是生搬硬套,但是模仿本来也是学习的一个过程,最重要的事情是在模仿中要学会思考。我也是设计模式的初学者,所以我会常用这句话来提醒自己,看过一句关于如何最好的使用设计模式的话:

“把模式装进脑子里,然后在你的设计和已有的应用中,寻找何处可以使用它们”。

这就有点像是张无忌练习太极拳了,忘了所有的模式吧,你已经在潜移默化的使用它了。当然如果你不学设计模式,你可能也在无意识的使用一些设计模式了,但是这个在跟学过以后再无意识的使用设计模式,应该隔着两重境界吧。

当然要达到这个境界,少不了大量的练习,当然也不能忘了设计是一门艺术,总有许多可取舍的地方

同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果,也就是说,给不同的对象发送同一个消息时,这些对象会根据这个消息分别给出不同的反馈。举个例子:假设家里养了一只猫和一只狗,两只宠物都要吃饭,但是吃的东西不太一样,根据主人的吃饭命令,猫要吃鱼,狗要吃肉,这就包含了多态的思想在里面,用JS代码来看就是:

设计模式的一些原则

  1. 找出应用中可能需要改变之处,把它们独立出来,不要和哪些不需要改变的代码混在一起(低耦合);

  2. 针对接口编程,而不是针对实现编程;

    关键在于多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上(在JavaScript中并没有超类型的概念。)我的理解是,接口可以理解为一个动作,而动作的具体实现则不用确定。这一点在下文讲解多态时会有一个更加具体的例子。

  3. 多用组合,少用继承

可能还有一些其它的原则,目前,我还没有涉及到,在之后的学习过程中,再补充。

let petEat = function (pet) { pet.eat()} let Dog = function () {}Dog.prototype.eat = function () { console.log('吃肉')}let Cat = function () {}Cat.prototype.eat = function () { console.log('吃鱼')}petEat(new Dog())petEat(new Cat())

需要了解的一些其它概念

要真正的理解设计模式,需要了解面向对象的一些基础概念:抽象,多态,封装和继承。
就JavaScript而言,由于是一门动态语言,在此不考虑抽象这一概念。

上面这段代码展示的就是对象的多态性,由于JS是一门动态类型语言,变量类型在运行时是可变的,因此一个JS对象既可以是Dog类型的对象也可以是Cat类型的对象,JS对象多态性是与生俱来的,而在静态类型语言中,编译时会进行类型匹配检查,如果想要一个对象既表示Dog类型又表示Cat类型在编译的时候就会报错,当然也会有解决办法,一般会通过继承来实现向上转型,这里感兴趣的可以去对比一下静态语言的对象多态性。

多态

概念:同一操作作用于不同的对象上时,可以产生不同的解释和不同的执行结果。

比如说有两只动物,鸡和鸭,当发出命令“叫”时,鸡会“咯咯咯“,鸭会”嘎嘎嘎“;

多态背后的思想是将“做什么”和“谁去做以及怎么去做”分离开来,也就是将“不变的事”和“可变的事物”分离开来。

多态的实现:归根到底是要消除类型之间的耦合关系,JS的变量类型在运行时是可变的,这意味着JS对象的多态性是与生俱来的。

多态的作用:通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句。

看一个例子来理解多态:

// 只使用谷歌地图
var googleMap = {
    show: function() {
        console.log('开始渲染谷歌地图');
    }
};

var renderMap = function() {
    googleMap.show();
};

renderMap(); //输出:开始渲染谷歌地图

// 好吧,谷歌在某些地方不好用,在某些要换成百度地图了,
var googleMap = {
    show: function() {
        console.log('开始渲染谷歌地图');
    }
};

var baiduMap = {
    show: function() {
        console.log('开始渲染百度地图');
    }
};

var renderMap = function(type) {
    if (type === 'google') {
        googleMap.show();
    } else if (type = 'baidu') {
        baiduMap.show();
    };
};

renderMap('google'); //输出:开始渲染谷歌地图
renderMap('baidu'); //输出:开始渲染百度地图

// 上述代码有一点的弹性,但是如果在更换地图,改变的地方太多了,这里就可以运用多态性这个理念了,这里也把做什么和怎么做分开了
// 抽象出相同部分,条件分支语句转化为对象的多态性
var renderMap = function(map){                  //做什么
    if (map.show() instanceof Function) {
        map.show();
    }
};

renderMap('google'); //输出:开始渲染谷歌地图
renderMap('baidu'); //输出:开始渲染百度地图

// 再添加一个搜搜地图
var sosoMap = {                               // 怎么做
    show: function() {
        console.log('开始渲染搜搜地图');
    }
};

renderMap('soso'); //输出:开始渲染搜搜地图

// 本例来自于《JavaScript设计模式与开发实践》

作用

封装

封装的目的是将信息隐藏,包括隐藏数据,隐藏实现细节,设计细节以及隐藏对象的类型等。

封装分为四类:封装数据,封装实现,封装类型和封装变化

封装数据:

在许多面相对象系统中,封装数据是由语法解析来实现的(private public proctected);

JavaScript中没有对这些变量的支持,只能利用变量的作用域来实现封装,只能模拟出public.private两种封装性。(ES6可用let),一般用函数来创建作用域。

封装实现
使得对象内部的变化对其他对象而言是不可见的,对象对其自己的行为负责,使得对象之间的耦合变得松散,对象之间只通过暴露API接口来通信,修改一个对象时,可以随意地修改它的内部实现,只要对外的接口没有变化,就不会影响到程序的其它功能。

封装类型
对静态语言而言,是一种重要的封装方式,把对象的真正类型隐藏在抽象类或者接口之后,Javascript没有这方面的支持,因此也没有这方面的需要;

封装变化:
把系统中稳定不变的部分和容易变化的部分隔离开来,在系统的演变过程中,我们只需要替换那些容易变化的部分,如果这些部分是已经封装好的,替换起来也相对容易,这可以最大程度的保证程序的稳定性和可扩展性

多态的作用是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句,举个例子:还是上面宠物吃饭的问题,如果在没有使用对象的多态性之前代码可能是这样是的:

继承

说到继承,JavaScript最重要的概念可能在于原型链。

基于原型链的委托机制就是原型继承的本质。

  • 所有的数据都是对象,JS的根对象是Object.prototype对象,它是一个空对象;
  • 要得到一个对象,不是通过实例化类而是找到一个对象作为原型来克隆它;
  • 对象会记住它的原型;就JS而言,对象的原型其实是其构造器的原型(new),包含在其隐藏属性_proto_中;
  • 如果对象无法响应某个请求,它会把这个请求委托给自己的原型。
    • JavaScript的原型最初都是由Object.prototype对象克隆而来;
    • 对象构造器的原型并不限于Object.prototype,可以动态的指向其他对象; A.prototype=obj
    • 原型链并非无限长,到顶(Object.prototype)以后,如果没有找到就会返回undefined;

本文是我学习设计模式的第一篇笔记,里面若有不恰当的地方,欢迎随时指出,也希望您看完本文,跟我一样有所收获。


参考书籍
Head First 设计模式
JavaScript设计模式与开发实践

let petEat = function (pet) { if (pet instanceof Dog) { console.log('吃肉') } else if (pet instanceof Cat) { console.log('吃鱼') }}let Dog = function () {}let Cat = function () {}petEat(new Dog())petEat(new Cat())

通过条件语句来判断宠物的类型决定吃什么,当家里再养金鱼,就需要再加一个条件分支,随着新增的宠物越来越多,条件语句的分支就会越来越多,按照上面多态的写法,就只需要新增对象和方法就行,解决了条件分支语句的问题

封装

封装的目的是将信息隐藏,一般来说封装包括封装数据、封装实现,接下来就逐一来看:

封装数据

由于JS的变量定义没有private、protected、public等关键字来提供权限访问,因此只能依赖作用域来实现封装特性,来看例子

var package = (function () { var inner = 'test' return { getInner: function () { return inner } }})()console.log(package.getInner()) // testconsole.log(package.inner) // undefined

封装实现

封装实现即隐藏实现细节、设计细节,封装使得对象内部的变化对其他对象而言是不可见的,对象对它自己的行为负责,其他对象或者用户都不关心它的内部实现,封装使得对象之间的耦合变松散,对象之间只通过暴露的API接口来通信。封装实现最常见的就是jQuery、Zepto、Lodash这类JS封装库中,用户在使用的时候并不关心其内部实现,只要它们提供了正确的功能即可

继承

继承指的是可以让某个类型的对象获得另一个类型的对象的属性和方法,JS中实现继承的方式有多种,接下来就看看JS实现继承的方式

本文由10bet发布于Web前端,转载请注明出处:学习设计模式前需要知道的事情

关键词:

上一篇:前端模块化(CommonJs,AMD和CMD)

下一篇:没有了

最火资讯