上一节,我们学习了几种提高模块级可扩展性的设计模式。本节主要学习如何使用设计模式Patterns来提高代码质量。提高代码质量的目的?高质量的代码是一切性能的基础。无论是可扩展性还是可重用性,都必须建立在高质量代码的基础上,让后续的所有操作都方便别人阅读和理解代码的用途。什么是代码质量?干净的代码是指我们的代码在命名、缩进等各方面都符合规范。没有冗余和繁琐的代码结构。语句通俗易懂,就是说我们的命名要符合语义,代码逻辑比较清晰。1.提高代码质量的设计模式1.1.优化代码结构的设计模式1.1.1。把这两种模式放在一起是因为这两种模式非常相似,状态模式相当于增加了状态管理的策略模式。用途:优化if/else分支应用场景:if/else分支太长,代码看起来会很丑。这时候可以使用策略模式或者状态模式来优化代码1.1.2。系统提供了一致的接口应用场景:当需要操作多个复杂的子系统来完成一个操作时,将这些复杂的子接口统一为一个更高层的接口来调用,完成1.2的功能。优化代码操作的设计模式1.2.1、迭代器模式的目的:我们在编程的时候不可避免的要对很多数组和对象进行操作。迭代器模式的目的是让我们可以方便地遍历数据,而无需访问对象或数组的内部。应用场景:当我们想对一个对象或数组进行操作,但又不能直接暴露其内部时,可以使用迭代器模式1.2.2。备忘录模式的目的:记住之前的状态,方便随时回滚。应用场景:通常用于缓存、状态回滚等操作。当系统状态多样时,为了方便我们回滚状态,我们记录状态,然后随时回滚。二、基本结构2.1.策略模式基本结构要求:假设你要写一个计算器,有加减乘除四种运算。不使用策略模式,我们会用ifelse来判断是什么操作,然后一层层操作。这段代码非常不优雅。使用策略模式改写代码,我们可以将加减乘除运算作为策略对象中的属性。这个时候我们就不需要用ifelse来判断了。我们只需要在策略对象上调用对应类型的属性方法即可。这样就省去了很多ifelse分支。代码示例:2.2。状态模式的基本结构状态模式和策略模式都是用来优化if-else分支的。状态模式可以看作是一种带有状态管理的策略模式,相当于把这些if-else的结构变成了对象内部的一个状态,然后改变对象的内部状态,使整个对象有不同的行为。代码示例:示例代码创建一个新的状态工厂,其中包含一个状态对象stateObject,其中包含策略模式中的policy对象,一个_status状态属性和一个run方法,改变状态工厂中状态对象的状态.状态改变后返回状态对象,使用时调用状态工厂的run方法执行对应状态的行为。通过改变状态,对象可以表现出不同的行为,而不是通过if-else分支来改变行为。这就是状态模式的基本结构和思想。状态模式文章推荐:https://zhuanlan.zhihu.com/p/...2.3。外观模式的基本结构我们在组织方法和模块时,通常会细化模块,将模块细化为多个接口。但是当我们给别人用的时候,我们需要把它组合成一个界面,就像在餐厅点菜一样。为了让用户有更高的选择,菜品可能会比较多,但为了方便一些选择有困难的用户,会提供一些套餐。和外观模式一样,界面被一个一个细化成小界面。最终的函数会被一个统一完整的接口调用。这就是外观模式的核心思想。代码示例:2.4。迭代器模式的基本结构迭代器的思想就是把一个数组或者对象变成一个迭代器对象,然后在不遍历数组或者对象的情况下,用一种方法顺序访问数组或者对象的内部。这简化了循环并简化了数据操作。代码示例:代码中创建了一个新的迭代器类。这个迭代器类接收传入的数组或对象。在其原型下有一个dealEach方法。这个方法类似于forEach。通过这个方法,我们不需要手动执行for循环的情况下,对数组或对象中的每一项进行方法操作。2.5.备忘录模式的基本结构备忘录模式会记录对象的内部状态。当我们需要的时候,我们可以根据记录的状态回滚到之前的状态或者方便对象的使用。代码示例:代码中创建了一个备忘录函数(Memento)。memento函数中有一个缓存对象(cache)用来缓存一些状态,然后返回一个函数。这个函数可以访问缓存对象,判断函数内部是否有这个函数。缓存,如果有则执行缓存的操作,如果没有则执行非缓存的操作。memo模式说白了就是用缓存对象记录某个状态,然后根据这个状态的存在做相应的事情。3.应用实例3.1.策略模式/状态模式示例3.1.1.动态内容要求:项目中有一个动态内容,根据不同的用户权限展示不同的内容。假设我们要显示三个内容。请求当前用户权限后,如果是老板,则显示所有内容。如果是manager,会显示第一条和第二条内容。如果是普通员工,则显示第三张。先看一个不使用状态模式的代码示例。上面代码的问题是有很多if-else分支。虽然目前只有三个角色,但是随着发展可能会出现很多角色,if-else会越来越多,所以我们使用状态模式来优化这段代码:首先创建一个状态类showControll,添加一个状态属性status和一个策略对象权力给类,然后把不同的角色放到状态对象中。设置状态对象和状态后,我们需要一个方法来显示状态,所以我们在showControll的原型中添加一个show方法,通过这个方法来改变状态。代码写完后,我们只需要新建showControll类,然后调用它的show方法就可以根据当前用户的权限显示不同的内容。与之前的代码相比,第一个好处是if-else分支少了,代码看起来更优雅。第二个好处是,如果我们要扩展更多的角色,只需要在策略对象权力中扩展策略即可。不错,写法更方便,更美观。这就是状态模式给我们带来的优化。3.1.2.复合动作要求:有一个小球,可以控制左右或上下移动。先来看一个反面的代码示例:上面的示例代码首先创建了四个移动方法,然后调用mover方法进行移动。如果这种方法只接收一个方向的运动就可以了。只有一个判断参数可以指定方向移动,但如果变成复合移动,这里接收左上、左下或右上、右下两个参数。这时候判断的情况有很多种,前、后、左、右四种方法的组合会产生很多种不同的情况。如果用if-else分支来做,会特别累。我们用状态模式来改造一下:状态模式首先需要把if-else判断变成对象中的状态,首先新建一个类。考虑到可能有左上移动,左下移动等,所以我们把状态status换成一个数组。它不是字符串。如果只有一种左移状态,则只会将一种左移状态放入数组中。如果是左下,则数组中会有左移和下移,然后新建一个策略对象,定义不同的移动方向。不同的方法向移动器类的原型添加一个运行方法。这个方法接收的是怎么移动的,所以我们把参数变成一个数组赋值给status,这样status就存储了移动状态,我们只需要循环这个status就可以了。数组,从策略工厂中找到对应的状态并执行。当它准备好使用的时候,我们只需要新建一个mover类,调用它的run方法传入移动的方向,比如让它向左向上移动,对比一下前后两段代码,就可以了很明显,代码的编写和简洁性都有质的提高。判断变成了对象的状态,根据状态执行行为,而不是顺序的if-else判断来执行行为。这是状态模式为了解决复合运动等复杂的if-else分支而做的一系列优化。3.2.外观模式示例3.2.1。插件封装要求:插件基本上是提供了一个高层接口供最终使用,也就是说它会有很多子模块和子接口,但是在使用的时候只需要调用一个高层接口插件终于用上了,界面没问题。假设我们有一个tab插件,它有很多子系统,比如初始化HTML的系统,改变tabs的系统,事件绑定系统等。代码示例:这些子系统有很多自己的子接口,别人要的时候使用这个tab插件直接生成tabs并使tabs生效,需要给他提供一个统一的接口。假设这个接口叫做init,我们在原型中添加init方法。该方法接收一个config,即配置参数。我们在init方法中调用子系统的接口,终于不用关心它有哪些子系统了。调用哪个子系统,只需要统一调用init方法即可完成整体功能。外观模式更像是一种指导思想。在项目中,我们不能避免一些底层的支持,比如开发插件和库。在编写这个库和插件的时候,要注意划分它的子模块,按照接口隔离的原则进行划分。接口和模块划分得更细,但是当有人使用我们的插件时,我们需要给他提供一个更高层次的、统一的接口,让他调用一个接口来完成整体的功能。3.2.2.封装方式要求:在兼容性要求还很严格的时代,我们通常需要进行能力检测。比如dom事件绑定有domlevel1和level2,在那个年代,往往需要检测浏览器支持哪个level,然后再使用相应的方法绑定事件。届时会采用将它们封装成方法的思路,将这些检测封装成一个统一的接口方法进行事件绑定操作。比如会有dom二级dom.addEventListener()、dom.attchEvent()、dom.onclick等一些绑定操作。代码示例:面对这样的绑定操作,我们通常要检测浏览器支持使用哪一个,到时候这个操作会被封装成一个方法,比如封装一个addEvent方法,需要接收dom(绑定元素),type(事件类型),fn(事件回调)三个参数,然后对浏览器进行能力测试,判断浏览器对绑定事件的支持情况。事件打包后绑定事件,不需要自己写能力测试。只需要通过外观方式统一封装一个接口即可。然后调用这个接口完成功能,就是外观模式的思路。3.3.迭代器模式示例3.3.1。构建您自己的forEach需求:forEach方法实际上是一个典型的迭代器方法。构建一个可以循环数组和对象的forEach方法。首先创建一个iterator类,这个iterator类接收一个数据,可以是数组也可以是对象,将接收到的数据挂在data属性上然后在Iterator类的原型中添加一个dealEach方法,该方法接收一个回调函数,我们先判断数据是不是数组,如果是数组,则进行for循环,在循环体中调用fn,给出每个循环的内容和下标;如果是对象,则进行forin循环,将value和key传给fn这样就搭建了一个简单的forEach循环,我们不用手动for循环数组或对象就可以得到每一项的内容。上文提到,forEach是迭代器模式的典型代表。从这里我们可以看出,迭代器模式就是给我们提供一种方法,让我们可以对数组和对象进行统一的操作,而不用自己手动遍历。3.3.2.给你的项目数据添加迭代器需求:在项目中,经常会遍历后端数据,为了方便遍历,封装了一个迭代器。假设我们有这样一个数据项目,必然要找到num等于2或者等于1的数组Whichobjectsarethere,查看一系列对象中有哪些对象具有满足某个条件的某个属性。经常执行这种操作。我们可以把这个操作封装成一个迭代器,代码示例:首先创建一个迭代器工厂,因为迭代器必须经常创建一个迭代器对象进行操作,所以用工厂模式进行封装,这个工厂接收一个参数数据,然后创建一个工厂内部新建迭代器对象,并将接收到的数据放到迭代器的属性上,给迭代器添加一个方法,让它可以在数据中查出符合要求的对象。假设我们添加一个getHasSomeNum方法,它接收两个参数,第一个是handler,第二个是num,handler可以是一个方法或者一个属性名,如果是一个方法,我们就用这个方法去查数据,如果是是一个属性名,用这个属性名来检查对象的属性名。在函数体中添加一个数组变量_arr,用于存放符合条件的对象,然后新建一个handleFn,判断传入的handler是否为方法,如果是,则赋值给handleFn,handleFn为自定义检查方法;如果没有,就把handleFn赋值给一个函数,让这个函数根据属性名来检查对象,这是默认的检查方式。判断对象的handler是否等于num,如果是则返回对象。这段代码实际上是享元模式。然后循环数据,调用handleFn传入每一项数据,并将返回值赋值给_result变量,判断如果_result不undefined(传入的item不满足条件返回undefined),将_result存入_啊;最后返回_arr,这样就可以直接调用getHasSomeNum方法查看数据了。使用时,调用iteratorfactory传入数据,调用getHasSomeNum方法传入属性名和待校验的值,过滤掉num为1的对象。如果需要自定义校验,例如,判断对象的值减1等于2,可以在调用getHasSomeNum时传入函数,自己判断。代码示例:通过将for循环封装成一个方法,让我们在处理复杂的数据检查时,不需要每次都手动遍历,这就是迭代器模式的思想。3.4.备忘录模式示例3.4.1。文章页面缓存需求:项目中有一个文章页面,现在需要优化。如果上一篇文章已经阅读过,则不会发起请求,否则请求文章数据代码示例:代码中创建一个pager函数,在函数内部新建一个缓存对象来缓存文章数据,返回一个函数让它可以读取缓存的数据,该方法接收一个pageName,即当前文章页面的名称,通过判断缓存对象中是否有这个pageName,如果有则直接返回数据,如果没有则发起一个以键值对的形式请求数据并将其存储在缓存对象中。这里的代码是使用备忘录模式完成文章缓存的思路。这段代码不能完全实现一个文章页面缓存。在真正的开发场景中,还需要做一些其他的事情,但是这是一种缓存页面的方式,缓存内容的思路是通过一个缓存对象将内容以键值对的形式缓存起来,然后只需要检查键名,如果不存在,则执行获取操作,将获取到的内容存入缓存对象;如果存在则直接返回内容,这就是备忘录模式的核心思想。3.4.2.前进后退功能需求:开发一个可移动的div,具有前进后退和回滚到上一个位置的功能。这个想法很简单。首先创建一个moveDiv函数,并在函数内部创建一个stateList来缓存之前的状态。状态的状态以一串数据的形式记录在数组中,然后创建一个指针指向当前状态。初始值为0,每次移动一个div,都需要一个方法,所以在prototype下增加了一个新的move方法。这个方法接收两个参数,一个是type(移动的方向),一个是num(移动的次数),我们假设有一个changeDiv用来移动div,在move方法的body中调用changeDiv传入type和num,每次div的位置变化后,将state推入stateList,所以我们推入一个对象,里面记录了type和num,同时也改变了当前指针(nowState)的状态,使其指向stateList的最后一位。上面的代码写完之后,前进后退就很简单了。只需要根据状态指针获取stateList中的上一个或下一个状态,然后再次调用changeDiv方法移动div即可。我们创建一个forward方法,在方法体中新建一个变量_state,判断当前指针小于stateList的总长度,也就是说当前指针不在最后一个地方,说明可以向前移动,然后加上当前指针,取出当前状态。stateList中对应的nowState项,并将取到的状态赋值给_state变量,然后调用changeDiv将当前状态调到div对应的位置改变。同理,只是改变指针的位置,然后取出对应的指针状态,再次调用changeDiv方法改变div的位置。这是大多数前向和后向函数对应的思想。无非就是把前面的状态和后面的状态存到一个数组里缓存起来,然后根据指针查看指向哪个状态,然后取出对应的状态。上面的代码实现是备忘录模式的一个很好的体现。本文由博客多发平台OpenWrite发布!
