当前位置: 首页 > Web前端 > HTML

DesignPatterns—关于如何提高代码复用(再也不用担心重复代码)

时间:2023-03-27 22:48:03 HTML

最后一节主要学习如何使用三种创建型设计模式。如何使用创建型设计模式来指导我们更好地封装代码,更好地创建对象。本节主要学习如何使用设计模式来提高代码的复用性。提高可重用性的目的?为什么要提高可重用性?提高可重用性能有什么好处?遵循DRY原则:英语就是不要重复自己。这个原则意味着你应该时刻记得检查你的代码中是否有重复的代码。如果是这样,将其抽象为公共部分。减少代码量,节省开销:遵循DRY原则后,我们的代码量会减少,内存开销也会逐渐减少,因为没有重复的代码块。什么是好的复用?在对象层面,设计良好、复用性高的代码的对象是可以重用的。我们不需要经常修改对象来使其满足新的要求。它是抽象的而不是具体的。执行相同操作的代码块不会重复多次。高度可重用的代码必须基于低耦合的模块。尽量实现模块之间功能单一。模块越简单,模块越容易被重用。1.提高可重用性的设计模式1.1.减少代码重复次数,高效重用代码设计模式桥接模式:和DRY原则很相似,都是把公共部分拿出来,而不是在现有代码中耦合,通过抽取公共部分,然后桥接来降低代码耦合度到使用它们的地方。目的:用桥接代替耦合应用场景:降低模块之间的耦合享元模式:通过观察相似代码块和对象之间的异同,区分公共部分和私有部分,然后将公共部分作为一个享元抽取出来减少对象或代码块。目的:减少对象/代码的数量应用场景:当代码中创建大量相似的对象和相似的代码块时可以使用享元模式1.2。创建高度可重用代码的设计模式模板方法模式:定义算法的运行框架,将一些步骤推迟到子类。允许子类在不改变算法结构的情况下重新定义算法的一些特定步骤。目的:定义一系列操作的骨架,即不具体实现操作,先确定操作的骨架,然后以这个骨架作为后续类似操作的基础,将骨架展开可视化实现后面的具体需求,简化以后相似操作的内容应用场景:当你在项目中有很多相似的操作时,它们的基本骨架可能是一样的,但是这些操作又各有特点。这时候我们可以使用模板方法模式2,基本结构2.1。享元模式的基本结构要求:有一百个不同文字的弹窗,每个弹窗的行为都一样,只是文字和样式不同。在这种情况下,我们不需要创建一百个弹出窗口,我们可以创建一个保留相同行为的弹出类,然后将显示弹出窗口的行为抽象成一个方法,然后接收要显示的弹出窗口类型;它们之间的区别在于文字和样式,所以我们可以将它们的区别提取为一个数组对象进行配置,然后我们只需要创建一个弹窗实例,通过循环调用弹窗实例的display方法即可通过数组,传入每个弹窗的文字和样式。代码如下:享元模式说白了就是当我们观察到对象或代码块中有多个相似点时,保留其中相同的部分,提取不同的部分作为享元,这样我们就可以用一个对象代替多个对象.Flyweight模式的核心是利用共享技术有效支持大量的细粒度对象。2.2.桥接模式的基本结构要求:有3个形状,每个形状必须显示三种颜色。在这种情况下,如果我们不注意可重用性,很可能会创建9种不同的颜色。形状。我们可以观察到:三个形状可能没有共同点,但是颜色有共同点,所以我们可以把它们的颜色提取出来作为一个公共方法,这些形状的类都依赖这个方法,用这个方法来显示color,这样我们只需要三个类就可以完成要求。例如,如果我们需要一个红色的圆圈,我们只需要一个新的圆圈类来告诉它它需要红色即可。综上所述,桥接模式就是把这些重复的部分作为一种常用的方法提取出来,然后再桥接回去。有点类似于builder模式,先拆分后合并,但是builder模式的核心重点是如何构建对象(focusoncreation),而bridge模式则着重于如何简化代码和改进代码通过桥梁实现可重用性(专注于功能)。2.3.模板方法模式的基本结构要求:编写导航组件,有的带有消息提示,有的是垂直的,有的是水平的,以后可能会添加其他类型。对于这个需求,我们不需要针对每一个导航组件都新建一个类,写一个基础的组件类即可。具体实现要等到具体使用的时候再说。代码示例:在代码示例中,首先新建一个基类baseNav,并在基类中设置基本骨架,包括其行为。同样的处理,先写出它的基本行为,然后预留一个回调,等到决定是消息提示类型还是其他类型,再用回调实现不同的地方和具体的操作和行为.三、应用实例3.1.享元模式示例3.1.1。文件上传需求:项目中有文件上传功能,可以上传多个文件。先看一个不使用享元模式的代码示例:上面的代码中,在不使用享元模式的情况下,我们需要上传四个文件,我们需要在新增的四个上传器类中指定这些文件。如果我们使用享元模式优化这段代码:提取公共部分和私有部分:我们观察一下这四个对象,它们的私有部分是要上传的文件,文件类型不同,而公共部分是每个对象有初始化,删除,上传功能方法,享元模式就是把这四个对象的私有部分提取出来作为公共享元,所以我们写一个数组,把这些对象的私有部分存起来,公共部分留下来,所以上述原型上的init方法、delete方法、upload方法可以复制保留,uploader类中的属性可以删去不要。我们将上传的文件和文件类型作为参数传入uploading方法中,经过这样的修改,我们只需要实例化一次uploader,然后循环遍历数组调用实例对象的uploading方法,传入文件和文件类型。再总结享元模式:享元模式是怎么工作的?其实就是观察代码中重复的部分,看哪些部分是private,哪些部分是public,然后把private的部分提取出来作为publicflyweights。享元的形式不一定是数组。它可以是一个对象或方法等等。这种想法就像是我们生活中一个很简单的道理。我们想试穿一百套衣服。不用请一百个模特。我们可以请一个模特来试穿一百套衣服。3.1.2.jQuery中的extend需求:extend方法需要确定参数个数来执行不同的操作。如果给jQuery的extend方法一个参数,比如一个对象,它会将对象扩展到jQuery。如果给它两个对象,它将合并这两个对象并返回它们。比如:对于这样的Operation,我们可能会想到先写一个extend方法,然后通过argument对象判断方法内部的参数个数,根据argument的不同长度进行不同的操作,比如:这样代码原则上没有错,但是可以发现这两个forin循环很相似。DRY原则告诉我们要尽量减少重复代码,所以我们使用Flyweight模式优化代码,提取公共部分和私有部分:这两个相似代码的私有部分接收副本的目标与副本的来源不同。我们先提取他们的差异作为外部公共享受,将接收副本的target(extend的第一个参数)作为外部享受,通过target存储,然后改变复制数据的来源,即,复制的人到源变量中。它们共同的部分就是forin循环,所以我们保留forin循环,forin循环应该循环我们的source,然后copysource到target。这里还是要做一个ifelse判断,判断如果参数的长度等于1,就表示扩展jQuery对象本身的属性,然后改变我们的flyweight,把target改成this,然后改变参数第一项的来源;如果参数的长度不等于1意味着应该将第二个参数对象复制到第一个参数的目标参数中,因此将目标分配为参数的第一项,并将源更改为第二项参数(代码勘误:上面的if判断应该是arguments.length==1)这样就成功的将两段for循环代码缩减为一段,通过提取不同的部分作为外部公共享元,然后只保留一段public运行,通过判断改变外部publicflyweight应该是哪个目标?它有效地减少了代码的重复。这就是享元模式的魔力。3.2.桥接模式示例3.2.1创建不同的选择效果需求:有一组菜单,每个选项有不同的选择效果。假设我们有三个菜单menu1、menu2、menu3,一般情况下我们会先新建一个菜单类,它接收一个文本内容,把文本内容放在class属性上,然后创建一个div,设置div的内容为文本内容,如:使用时会创建menu1、menu2、menu3三个实例,传入对应的内容。然后将它们绑定到不同的事件上,达到不同的效果。在不使用桥接模式的情况下,我们需要创建三个对象,并为这三个对象绑定不同的事件来改变颜色。重码率比较高,不符合高复用性原则。使用桥接模式优化上面的代码:提取公共部分:上面代码公共的部分是设置颜色和事件绑定,我们提取这部分并桥接到源对象:首先在menuItem类,然后接收一个color参数(就是我们要桥接的对象),然后把接收到的color参数桥接到menuItem类的color属性,创建一个menuColor类,它接收两个参数colorover(鼠标的颜色hover),colorout(鼠标移开的颜色),并将接收到的两个参数放在menuColor类的属性上,因为dom已经放在了menuItem类的属性上,所以我们可以通过绑定dom事件一个方法,我们给menuItem类的原型添加一个bind方法,负责绑定dom事件。绑定事件后,我们不需要关心它会使用哪种颜色。我们只需要直接取出桥接的颜色对象(this.color),所以我们在事件绑定的外层创建self变量赋值给this,因为事件中的this指向dom对象,所以我们只需要在onmouseover事件中为menu对象赋值this.style.color,也就是self.color对象上的colorOver属性就够了,在onmouseout事件中也是如此。这样在实现需求的时候,我们只需要定义一个外部数据数组,它定义了菜单的文字,悬停和移出的颜色,使用的时候,循环这个数组创建一个menuItem实例,传入数据的内容,然后创建我们要桥接的颜色对象,告诉它悬停和移除颜色是什么,并将这个对象传递到menuItem实例中。最后调用bind方法自动绑定事件。通过对比,我们可以发现我们重复的代码变少了,代码复用性更好了。这就是桥接模式的作用。面对这样的问题,我们将变化的部分和不变的部分分开,然后将它们桥接在一起,以应对后续的多维变化。这就是桥接模式的目的及其好处。3.2.2.Express中创建get等方法的要求:express中有get、post等七八种方法。如何方便快捷地创建它们。比如我们可以在express中通过app.get为get请求设置一个中间件,通过app.post为post请求设置一个中间件,通过app.delete为delete请求设置一个中间件。我们要给这个exampleapp给这些方法先来看一个反面例子:很多人会这样入手,创建一个express类,在它的原型中添加get、post、delete方法。这是一个非常典型的例子。我们的系统可能会向多个维度发展。比如,因为你后面可能需要添加put等请求方法,这些请求方法有很相似的部分,所以你会在原型链中重复添加方法,导致很多重复的代码,如何优化呢?这是在express源代码中完成的。它首先会有一个方法数组。这个数组是通过第三方库输入的,然后在源码中require。在demo代码中,我们会直接创建这个数组,把代表请求的几个方法名写入数组,然后循环数组,在循环体中为express实例注册这些方法,这样我们就不用写了反复这么多方法。关于具体的功能,它是借助一个路由对象来桥接功能,直接在注册方法中调用route[method]然后将参数传入路由也是类似的循环。复制源码后可以看到路由中的方法也是通过一个循环注入的,循环里面对应我们有什么方法。操作调用相应的中间件,有效减少重复代码,提高代码的复用性。这个方法相当于直接调用get或者post,然后get和post使用桥接的路由对象方法完成功能,路由对象上的方法也是桥接完成的。路由内部的桥接其实就是上面源码中的一整段函数。在这个函数中判断是什么方法,中间调用对应的方法以上就是桥接模式在express中优化代码的应用。3.3.模板方法模式示例3.3.1。写一个弹窗组件需求:项目有一系列的弹窗,每个弹窗的行为、大小、文字都不同。如果我们写一个消息类弹窗,发送请求类弹窗,删除操作类弹窗,我们可能会新建一个消息类弹窗类,发送请求类弹窗-up窗口类,用常规思维删除操作型弹出窗口。窗口类,这个方法是不必要的,虽然它们的行为和大小不同,但是它们有很多共同点,比如它们总是一个弹出窗口,它们想要弹出,点击确定或者取消它们它们都有消失等等。这是他们的共同点。参考模板方法模式的思想,先将它们的共同点提取到一个基本的模板类中,定义了word、size、dom属性,并赋值给传入的word和size,dom初始化为null,然后定义了基本的行为方法,比如显示弹窗的初始化,在方法体中创建div,div的文本赋值为this.word,div的样式赋值为size的属性。这是针对每一个有弹窗的,它必须有一定的宽高和文字,然后把this.dom赋值给这个div然后定义基本的操作方法。比如点击cancel后所有的弹窗都要隐藏,方法body中div的显示会改成none,当然我们还有一个确认操作,但是我们没办法确定会是什么点击确定后做,所以我们首先确定确认操作的基本行为。首先点击确定也隐藏弹出窗口然后我们定义一个特殊类型的弹出窗口。比如定义一个ajax类型的弹窗,点击确定后发送ajax请求。我们创建一个ajaxPop类,这个ajaxPop类继承基类basePop(可以通过调用类内部的其他类实现继承)然后扩展ajaxPop类的行为,并将基类的实例赋值给类的原型ajaxPop类继承基类Behavior,然后获取ajaxPop类之前的隐藏行为,然后定义其新的隐藏行为方法,在新方法中调用之前的隐藏行为方法,再添加ajaxPop类的特殊隐藏行为,比如点击取消后会打印1(装饰器模式的体现)确认弹窗同理,先把之前的确认行为拿出来,然后重写它的确认行为,在重写的方法中调用之前的确认行为body,然后添加特殊的确认行为,比如发送ajax请求。(装饰器模式的体现)这样我们就可以用最少的代码创建和扩展不同类型的弹窗。这种方法很像面向对象中的继承,而模板方法模式不一定要通过继承来实现,有很多种实现方式。它强调的不是一定要有继承和模板,而是强调先定义后面需要执行的一系列不同维度的操作的基本行为,然后在这个基本行为中给操作赋予扩展的空间,这就是模板方法模式的目的和作用。3.3.2.封装一个算法计算器需求:现在我们有一系列自己的算法,但是这个算法往往需要在不同的地方加入一些不同的运算。比如a页要先加两个数再计算,b页要先减两个数再计算。也就是说,这个算法计算器有一系列的基本算法,而这些基本算法在不同的地方有不同的运算。这种需求是一个非常典型的例子,可以使用模板方法模式来解决。代码示例:首先,我们创建一个计数器类来定义计数器方法的基本计算,也就是先搭建算法骨架,在原型中加入count方法。这个count方法就是计算的时候调用的方法,它会接收计算出来的数据。在该方法中,首先定义一个基本方法baseCount。假设baseCount先加num+4,再加num*4。这样,我们就定义了基本的计算。模板方法模式是先定义基础,再扩展。如何扩展它的基本计算?所以我们需要预留两个扩展方法。相对于之前案例中弹窗演示的继承方法,这里我们可以使用方法组合来完成扩展。首先在计数器类的原型上定义两个方法,一个是基础算法计算之前要进行的计算,一个是基础算法计算之后要进行的计算。这两个方法类似于axios的请求拦截器和响应拦截器,通过addBefore和addAfter添加一些方法,这些方法会在baseCount调用前后起作用,所以我们需要在counter类中添加两个队列来存放添加的方法基本算法计算前后。然后在addBefore和addAfter方法中分别push对应的方法。扩展方法之后,我们需要重写计算方法,让它先执行beforeCounter队列,计算完再执行afterCounter队列。在count方法中,先定义变量_resultnum,初始等于传入的num,然后定义一个_arr变量,先把_arr初始化为[baseCount],这个_arr就是我们要执行的方法队列,然后我们会拼接beforeCounter_arr然后拼接afterCounter,这样整个调用就形成了一个队列beforeCounter=>baseCount=>afterCounter接下来,我们只需要从头到尾执行这个队列,传入数据进行计算,然后将结果交给_resultnum,所有的方法都执行完后,返回最终的计算结果_resultnum。代码写好后,使用起来非常简单。假设在a页基础计算前做减法,基础计算后数字乘以2。我们可以先新建一个实例化对象,调用它的addBefore方法,在回调函数中进行减法运算,然后返回计算结果;然后调用addAfter方法,在回调中将结果乘以2返回;然后调用我们的基本计算方法count,传入要计算的数即可。这样可以保证页面a既有我们的基本算法,也有它的具体算法。上面的弹窗例子使用了继承的方式。本例中使用组合方法,将我们要扩展的东西组合成方法来实现扩展。不管用哪种方法,都是为了找到相似的部分,然后定义基本的操作骨架,然后根据操作骨架给出扩展接口,这就是模板方法模式的核心思想。再次强调,无论什么设计模式,首先要记住它的核心思想,而不是它的行为。最后,了解一下JavaScript的组合和继承组合①JavaScript最初并没有特殊的继承,所以最初的JavaScript推崇函数式编程,然后进行统一组合和桥接在一起②桥接模式可以看作是combination,将不同的事物组合起来产生一个新的对象或一个完整的功能。组合的优点是耦合度低,复用方式方便,扩展方便。它的缺点是需要手动一个一个组合,不能像继承一样自动完成。继承①在ES6中class和extend出现之前,也可以实现继承。实现它的方法有很多种,但每种方法都有其自身的缺点。②模板方法模式可以看作是继承的一种表现形式。继承的好处是可以自动获取父类的内容和接口,方便统一。继承的缺点是不方便扩展。如果使用子类来继承父类,父类发生变化时子类也会发生变化,非常不利于后续的扩展。一般来说组合大于继承。我们更喜欢使用组合而不是继承。如果可以用组合来解决问题,最好不要用继承。下一篇:设计模式——学习提高扩展性(方法层面)(更从容应对需求变化)本文由博文平台OpenWrite发布!