上一节我们主要学习了使用设计模式编写代码的指导思想和设计模式的分类。本节主要学习三种创建型设计模式的使用方法。如何使用创建型设计模式来指导我们更好地封装代码,更好地创建对象。为什么要打包?包装能给我们带来什么好处?定义变量不会污染外界:封装的首要目的是保护我们的变量不被外界污染,不会污染外部变量。在大型项目中很难保证变量名不和别人的变量名冲突,所以说这个很重要。可以作为一个模块来调用:除了以上几点,良好的封装性可以保证我们的代码作为一个独立的模块与外界进行通信。将程序变成模块架构的基础是良好的模块封装。遵循开闭原则:对修改关闭,对扩展开放。我们的代码应该允许用户扩展但不允许用户修改。开闭的原理是基于封装的。只有在封装良好的情况下,才能保证代码不能被直接修改。什么是好的包裹?首先要保证我们的内部变量对外不可见,也不能被修改,也不能直接获取,因为模块一直在和外部通信,所以只需要防止内部变量模块不被外部直接调用,所以我们需要留出两个接口,第一个是为外部调用函数预留的接口,第二个是为某个扩展接口预留的,方便别人扩展该模块的代码和功能。总结一下,一共有六个字:不可见,离开接口1.封装对象时的设计模式我们将介绍三种帮助我们封装对象的设计模式。这三种设计模式可以帮助我们非常有效地生产对象。这里为什么要把对象和封装放在一起呢?原因在于,应用设计模式在产生对象的时候,实际上已经实现了更好的封装。1.1.创建对象时常用的两种设计模式。工厂模式:既然叫工厂,其实就是创建一个专门用来创建对象的工厂。我们使用这个工厂来获取对象,这样我们就可以使用new操作符来代替我们或者替代我们。手写生成对象。用途:方便我们创建大量的对象。应用场景:工厂一定要大批量生产某种东西,所以当你需要创建的对象需要大批量生产的时候,首先应该想到工厂模式。比如我们项目中有很多弹窗。我们应该把我们的弹窗组件封装成一个工厂模型,供其他人使用。当其他人需要调用弹窗时,只需要调用工厂获取弹窗对象即可。建造者模式:工厂模式用于生产大量相似的对象。这些对象可能只是内容不同,其他部分大致相同,而建造者模式则完全相反。我们首先想到的是建造房屋作为建造者。盖房必须精建。可以大量生产房屋,所以建造者模型实际上是一种精细化的建造理念。目的:指导我们通过组合构建一个复杂的全局对象应用场景:当我们需要创建一个单一的、巨大的组合对象,可能不需要大量输出时,可以考虑使用构建器模式,对应工厂模式分别。输出对象的两个方面。比如:写一个复杂的轮播,轮播一般只有一页,轮播有各种动画效果,各种复杂的交互。这时候用builder模式来封装代码就很好了。以上就是帮助我们创建对象的两种设计模式。创建对象后,我们往往需要一些特殊的要求,例如:我们需要保证世界上只有一个对象。1.2.创建世界上只有一个对象的设计模式单例模式:为什么要确保全局只有一个对象?因为在很多情况下,为了避免多对对象之间的干扰,我们往往需要保证全局只有一个对象。目的:保证全局只有一个对象。应用场景:为了避免重复创建,避免多个对象相互干扰。比如:当项目中重复new同一个类会造成多个对象之间的干扰,此时可以考虑使用单例模式。二、基本结构2.1.工厂模式的基本结构工厂模式就是写一个方法,你只需要调用这个方法,就可以得到你想要的对象,例如:工厂方法就是在工厂模式下要写的工厂,以及工厂要做的事情很简单,通过参数告诉工厂我想要什么样的对象(本例中是type),根据工厂内部的参数判断我想要的是什么对象,然后在内部创建对象和把它返还。2.2.建造者模式的基本结构建造者模式就是将一个复杂的类的各个部分拆分成独立的类,然后在最终的类中将它们组合在一起,例如:建造者模式强调建造,就像我们盖房子一样,我们将使用预制板、梁和其他预制的东西来建造我们的房子。因为建造者模式的内部结构比较复杂,所以在编写建造者模式的时候,里面还会有很多其他的类,就像上面代码中写的Model1和Model2一样,可以看作是建造的楼板和梁一个房子。最后拿出来给别人使用的类,内部会由Model1和Model2组成,也就是我们所说的一个复杂的类,拆分成独立的类,然后组合起来形成最终类,Final就是最终类。2.3.单例模式的基本结构单例模式是由一个方法定义的,在使用时只允许获取内部存在的同一个实例化对象。比如单例模式的做法不是很固定,更重要的是,记住全局只有一个对象。比如代码示例中的Singleton,就是实例化为单例的对象。Singleton对象下挂载了一个getInstance方法,只能通过该方法获取该类的实例化。目的。在这个方法中,首先判断this上是否有实例属性,如果有则直接返回这个属性,如果没有则将这个属性赋值给实例化的Singleton并返回。这样我们就通过调用getInstance方法得到了实例对象。如果它已经被实例化,我们将得到之前实例化的对象。如果还没有实例化,我们就实例化这个类。实现标准的单例模式并不复杂。无非就是用一个变量来表示某个类是否已经创建了一个对象。如果是这样,下次获取该类的实例时,将直接返回之前创建的对象。3.应用代码示例3.1.工厂模式示例3.1.1。彩色弹窗需求:项目中有弹窗需求,弹窗有很多种,在内容和颜色上也有区别。假设我们有一个信息弹窗,一个确认弹窗,一个取消弹窗。如果我们需要创建三个info弹窗,三个confirm弹窗,三个cancel弹窗,每个的内容和颜色都不同,没有factory模式的情况下,我们很可能会这样做:新建三个Info弹窗,newinfoPop然后传入内容和颜色,然后复制粘贴一大堆,写起来很麻烦。例如:我们使用工厂模型改造创建一个弹窗工厂,然后传入弹窗类型、内容和颜色参数,在工厂中判断我们想要什么类型的弹窗,返回什么类型的弹出窗口。这样,在创建工厂后,我们只需要告诉工厂我们需要创建弹窗对象的弹窗类型即可。代码这样写完之后,这部分代码就可以封装成插件的代码了。封装非常简单。将代码放入匿名自执行函数中,然后指向对外暴露的工厂(挂载在全局对象下),外部使用的用户可以直接调用该工厂而无需关心new哪个弹窗。如果弹窗较多,可以将弹窗配置成一个数组。在数组中确定你要的是什么弹窗,弹窗的内容和颜色,然后循环数组调用工厂。上面的弹出工厂还是有问题。如果用户在不知情的情况下去新建工厂,可能效果不好,所以我们修改一下工厂,判断一下这个是不是弹窗工厂pop。如果是则表示用户使用了新的运营商。这个时候,给它一个new的实例。该实例是这种类型。如果不是,则说明用户正在直接调用它。去弹窗工厂,让它去进程。去。这个时候就不用switch做判断了。可以删除所有switch代码,在pop工厂原型上挂载infoPop、confirmPop、cancelPop方法。这样无论是以后需要扩展不同类型的弹窗,还是减少弹窗,都会方便很多,因为我们只需要修改原型链即可,而对于之前的switch的写法,需要修改switch还有一层判断要加。改成这种写法,只需要修改原型即可。这是加了健壮性判断和可扩展性的工厂代码。3.1.2.源码示例——jQuery需求:jQuery需要操作dom,每个dom是一个jQuery对象。jQuery将dom包装成jQuery对象,方便我们操作dom。我们要操作那么多dom。如果每个jQuery对象都需要new,这样的操作会很麻烦。因此,jQuery本身的包装就采用了工厂模式。我们只需要调用jQuery的$方法就可以得到jQuery对象了。jQuery源码是如何构造这个工厂的?首先在外层封装一个匿名自执行函数的代码,然后在自执行函数内部定义一个暴露的jQuery方法,它接收一个选择器和上下文,然后将jQuery方法挂载为$符号和jQuery到window对象上,也就是把工厂挂载到window上。jQuery工厂内部返回了一个jQuery.fn下的init方法,也就是说得到的对象其实是通过jQuery.fn.init创建的实例对象。这里之所以没有使用新的jQuery类本身,是因为它会形成死循环。的递归。然后让jQuery.fn等于jQuery.prototype,因为所有方法都会挂载到prototype下。这里,我们利用引用的特性,让fn等于原型,原型上的所有方法都被引用了。然后在原型下创建init方法。由于我们最终得到的实例化对象是init的实例化对象,所以init类的原型链应该等价于jQuery自身的原型链,所以jQuery.fn.init.prototype等于jQuery.fn,即EquivalentjQuery的原型相对于jQuery的整个架构,它的各种方法和模块是如何扩展的?它有一个扩展方法。这个方法的作用是复制。如果只传递一个对象,它会将对象复制到jQuery。在上面的代码示例中,复制到jQuery的fn也相当于复制到它的原型链,也相当于复制到init的原型链,然后通过extend可以将各种模块复制进去。比如css方法,animate方法等都会通过这个方法复制进来。以上就是jQuery架构的实现。它其实是一个工厂模式,只是它构造工厂的方式和我们前面演示的代码有点不同,但是它逃不过一件事,就是通过调用方法自动给我们想要的东西。对象来替换我们想要新建的对象。这样做带来的第一个好处就是方便了我们的操作。比如在jQuery中有大量dom操作的情况下,没必要一个一个new。二是我们不需要详细知道要new哪一个,我们只需要告诉工厂它需要Which会做什么。通过以上两个案例不难发现,工厂模式就是把真正需要暴露的对象先封装起来,然后只暴露一个工厂方法,让用户通过这个工厂方法获取对象。它的好处是方便我们创建大量的对象。3.2.建造者模式示例3.2.1。编写一个编辑器插件需求:有一个编辑器插件,初始化时需要配置大量参数,内部函数较多。(搭建架子,细节未实现)编辑器插件有很多功能,比如我们可以前后移动,编辑字体颜色等等。面对如此复杂的编辑器插件,我们使用构建器模式是非常合适的,因为我们要编写的编辑器插件:只需要少量的编辑器对象。一般来说,一个编辑页面只需要一个编辑器;它在初始化的时候需要很多参数,所以工厂解析很多参数会花费很多时间;其内部功能复杂,可能由多个模块组成。这时候就需要一种精细化的建造者模式。编辑器会有一个final类,也就是说在使用的时候需要这个新的类(这里是Editor类)。建造者模式就是把它的模块拆分成独立的类,然后组合起来。为了完成我们的Function,需要拆分出哪些类呢?首先我们需要一个初始化类,因为我们的编辑器插件必须有html结构,所以我们创建initHTML类;然后创建一个控制字体大小和颜色的类fontControll;我们还有forward和backward功能,此时需要一个状态管理类来管理当前内容的状态,所以在状态管理类stateControll模块创建和移除之后,再为这些类定义方法,比如initHTML类的initStyle方法初始化编辑器的样式,最后将编辑器渲染成domfontControll类的renderDom方法有改变字体颜色的方法changeColor,改变字体大小的方法changeFontSize,例如,状态管理类stateControll有保存状态的saveState方法,回退状态的stateBack方法,推进状态的stateGo方法。我们把最后把别人使用的类Editor挂载到window对象上,然后把拆分出来的类合并到Editor类中。当我们将这些独立的模块构建到Editor类中时,这些模块就可以相互调用了。比如我们现在添加状态回滚功能,只需要取出当前状态即可。首先在stateControll类中定义一个状态数组来存放前进和后退状态,然后创建一个状态指针指向当前状态。在状态回滚方法stateBack中,只需要从状态数组中取出当前状态的前一个状态即可。然后调用字体控制模块fontControll来改变字体。在这样的组织下,各个模块之间的通信就会变得非常清晰。我们把一个复杂的功能插件解析成几个独立的小插件,最后组合起来。在一起,不仅方便我们写代码,也方便我们组织模块之间的通信。保持模块之间的低耦合,从而实现高效的通信和高效的编程,这就是建造者模式的目的。3.2.2.Vue的初始化需求:Vue内部模块较多,流程复杂。有需要的可以自行阅读源码。Vue内部的构建方式也可以看成是构建器模式。它的构造过程是这样的:首先创建Vue类,为了防止用户通过new操作符调用它,需要在里面使用instance来判断这个是不是Vue,如果不是就说明是new操作符没有使用,需要抛出一个警告,告诉用户此时没有使用新的运算符。如果是,则调用init方法传入用户在初始化后传入的配置参数options,完成一次配置初始化。生命周期、事件系统、渲染函数等众多与vue相关的功能是如何注入到这个极其简单的Vue类中的呢?在vue源码中,调用了一系列的初始化方法混入,例如:通过调用这些方法混入到Vue类中,和上面的例子一样,将模块分离成单独的类,最后放入它们在Editor类中,只不过在Vue中将写法改为方法调用,直接写在上例中的构造函数中。Vue类本身非常简单。它的所有功能都是独立开发,然后通过一系列混音完成的。可见,Vue也是使用构建器模式来构建对象的。3.3.单例模式示例3.3.1。写一个数据存储对象要求:项目中有一个全局的数据存储,并且只能有一个存储,否则需要同步,会增加复杂度。在多方工作的情况下,如果用户不小心new了这个对象,可能会导致双方数据不一致,需要同步数据,无形中增加了复杂度。这样的全局存储对象必须要变成单例模式。假设我们的全局存储对象是一个store,首先创建一个store类,然后在内部创建一个store变量来存储数据。我们要保证无论怎么新建store类都只能返回同一个对象,可以通过store下的install属性来判断。如果有这个属性,直接返回。如果不是,store.install属性将被指定为this,并且在使用new运算符时this将指向商店本身。然后我们在store类之外将install属性初始化为null。代码示例如下:通过上面的代码,我们可以发现无论怎么新建store类都得到同一个对象,例如:代码示例中创建了两个store,因为它们都指向同一个对象。当修改s1的属性时,s2的属性也随之改变。此时如果去掉单例模式并验证,将store类的代码修改为:运行结果如下:此时,因为每次new都会创建一个新的对象,所以s1和s2做不指向同一个对象,导致要维护两个数据副本。单例模式的目的是为了减少多个对象的干扰,让我们的编程更简单。3.3.2.vue-router源码解析要求:vue-router必须保证全局只有一个,否则会乱码。如何保证vue-router源码中只有一个global?代码如下:以上代码是防止重复注册vue-router的代码。我们在使用vue-router的时候,会调用vue.use方法注册vue-router。其实我们在调用vue.use的时候会执行install方法。所以只要确保install方法在每次调用的时候都会检查是否使用过,如果使用过则不会执行后面的代码。它的实现也很简单。在外层定义一个_Vue变量。install方法每次都会收到一个参数vue。这个参数是Vue类,然后内部判断install方法下是否有installed属性,_Vue等于Vue类,直接返回即可,不执行后面的内容。如果没有installed属性,说明还没有赋值。把installed属性设置为true,然后把外面定义的_Vue变量赋值给Vue类,这样下次调用install方法时,直接进入判断条件,中断执行。这与我们之前演示的单例模式非常相似。是通过方法的一个属性来判断的。vue-router中多加了一个判断,即外部_Vue变量是否等于传入的Vue类,通过多重判断来保证代码安全。下一篇:设计模式——关于如何提高代码复用本文由多发博文平台OpenWrite发布!
