上一节主要学习了方法的可扩展性以及如何更好地扩展方法。本节主要学习模块Scalability,以及如何更好地扩展模块。我们可以把任何程序看成一个模块+组织模块通信。模块是组成程序的单元。各种模块加上它们的通信构成了我们的程序。这个过程很像我们生活中的开房。对于餐厅,我们需要一个厨师模块、服务员模块、点菜模块等,这些模块之间相互通信。order模块告诉waiter模块,waiter模块告诉chef模块。厨师模块就绪后,告诉服务员模块。沟通方式构成了餐厅的运作方式,流程也是如此。模块怎么做?这是设计模块的想法。这个想法不是固定的。如果需要,您可以根据自己的想法进行设计。这个思路是在你不熟悉模块设计的时候提供一个指导思想。首先我们拿到需求,然后分析完成需求需要哪些步骤。比如将大象放入冰箱需要哪些步骤,分析需要哪些模块来完成这些步骤?完成模块,组织模块,传达这个思路,然后借用开餐厅的例子,需求就是开餐厅。开餐厅,需要有人招待客人,做菜,知道客人点什么。服务客户,我们必须要有服务员模块。要做饭,我们必须有一个厨师模块。要知道顾客点什么,我们需要点菜。模块,然后我们完成这些模块,然后安排这些模块之间如何工作。提高整体项目可扩展性的核心?低耦合和良好的组织沟通。低耦合模块是整个程序层面可扩展性的基础。只有当你的模块被划分成低耦合的时候,你的项目才会有很好的可扩展性。模块划分之后,还要组织它们之间的通信。这是第二个方面。要组织好它们之间的通信,一个好的项目代码就是把模块划分成低耦合的,组织好它们之间的通信。我们通常所说的架构就是设计如何将项目划分成低耦合的模块并组织通信。1.提高可扩展性的设计模式1.1。响应需求变化的设计模式(一)1.1.1.观察者模式目的:减少模块之间的耦合,提高可扩展性。比如有两个模块a和b,如果模块a直接和模块b通信,那么两个模块a=>b的耦合度比较高,相当于我和你面对面交流。当我们要分开时,我们不打招呼。不客气,加入观察者模式后,a=>observer=>b,模块a通过观察者向模块b发送消息,相当于我和你之间用微信交流,微信就是观察者,这时候我们如果想要分开,不需要回复对方的消息,降低了耦合度。应用场景:当两个模块直接通信时,它们的耦合度会增加,不方便直接通信时不方便使用观察者模式。不方便直接通信的典型场景是异步。比如我有一个请求发送到后端,那么如何保证它能和同步模块通信呢?再比如,如果绑定了点击事件,那么怎么知道点击事件什么时候触发呢?就像你不知道异步请求的结果什么时候回来一样,你也不知道什么时候会触发点击事件。如果此时模块a是异步的,模块b是同步的,那么同步模块b和模块a通信肯定是不方便的。回调会增加它们之间的耦合度,所以使用观察者模式是非常合适的。b模块首先在观察者上注册一个观察方法。当模块a的异步任务完成后,会通知观察者,然后观察者将消息传递给模块b,解决了他们直接通信不便的问题。另一种场景是,比如有两个模块从来没有想过要通信。当你写代码的时候,你永远不会想到有一天这两个模块会通信。随着产品的开发,他们会互相交流,如果强行改了他们的代码让他们通信,别说会增加多少工作量和复杂度,增加耦合度也是必然的。通过观察者模式,双方的变化会比较少,因为一开始并没有想过通信,也就是说彼此没有通信接口。突然,如果产品说要沟通,你必须为对方添加另一个。通信接口,所以修改成本比较高,可以通过观察者模式降低修改成本。1.1.2.ChainofResponsibilityMode责任链模式与Observer模式相反,它更多的是用来组织一系列的同步模块。目的:为了避免将请求发送者与多个请求处理程序耦合,将模块组织成一个链。比如开一个水果加工厂,我们把水果加工环节组织成一个链条,先经过清洗模块,然后清洗完水果交给切果模块,再把水果加工模块交给水果处理模块,就像流水线一样,我们也把要做的事情一个一个组织起来,然后在这个链条上依次完成任务和传递消息。应用场景:把操作分成一系列的模块,每个模块只处理自己的事务。这种组织方式的好处是你的操作被分成了一系列的模块,每个模块只处理自己的事务。这时候你只需要添加一个链接就可以添加新的东西,改起来更舒服。以上面的水果加工厂为例,我们现有的流程是清洗=>切水果=>加工水果。如果检查突然严格,则必须在水果清洗后增加一个消毒环节。如果我们的流程是责任链,只需要在链上加一个消毒,那么流程就是清洗=>消毒=>切水果=>加工水果,加一个环节对前后的处理模块没有影响.如果不组织成责任链,模块会很乱。每个模块都负责切水果和洗水果。这时候如果要增加消毒功能,就必须给水果加工厂的各个模块增加消毒程序。变得非常大。1.2.改变需求的设计模式(二)1.2.1.访问者模式目的:解耦数据结构和数据操作。例如,我有一系列数据。我以前是直接操作数据,data=>operation,加入visitor模式后,data=>visitor<=operation,数据给了visitor,操作也给了visitor,然后access了运算符通过输入操作对数据进行操作。应用场景:当数据结构不希望与操作关联时,可以使用访问者模式。不想与操作关联的数据结构是什么?假设有10组数据完全不同的数据结构和10个完全不同的操作。数据多变,数据结构不稳定,操作也不稳定。在这种情况下,我们很难定义如何操作,所以我们可以通过访问者将数据和操作都传递给访问者,我们可以在想要操作和操作任何数据时,将具体的命令传递给访问者。访问者模式很少用到,因为我们在写程序的时候,往往追求数据的结构稳定。如果程序中的数据结构不稳定,说明程序在设计上有问题,所以访问者模式是非常慎用的,因为在开发项目的时候,基本都是直接操作数据,而这种不稳定的数据结构不会出现在正常代码中。二、基本结构2.1.观察者模式的基本结构。观察者模式的核心是定义一个观察者。这个观察者并没有说具体绑定到某个模块和某个模块。这是全球通用的事情。,到时候需要注册观察哪个模块,来这里注册,你要触发观察哪个模块,来这里触发。一般适用于不方便的直接通信或异步操作。观察者的两个基本元素是regist和fire。这两个元素满足观察者的基本结构。如果你愿意,还可以加上remove等操作,regist相当于把你的monitor注册到observe的消息对象上,fire相当于你要触发哪个listener,直接调用即可。2.2.责任链模型的基本结构责任链模型就是把我们要做的事情组织成一系列的模块,然后依次调用这些模块来传递消息。适用于不涉及复杂异步的操作。假设我们要做的事情组织成mode1、mode2、mode3,这三个模块依次处理_result,就是责任链模型的结构。2.3.访问者模式的基本结构访问者模式就是定义一个访问者来接收数据并进行操作。适用于数据和操作不稳定的情况。data变量是我们的数据,handler是我们的操作,vistor是我们的访问者。将数据和操作交给访问者,允许操作访问访问者中的数据。通过访问者而不是操作直接访问对象,减少了数据和操作之间的耦合。3.应用实例3.1.观察者模式示例3.1.1。多人协作问题需求:假设A工程师写了首页模块,B工程师写了评论模块,现在评论要显示在首页。假设A工程师写的模块叫做index,B工程师写的模块叫做comment。在设计这两个模块的时候,他们从来没有想过它们之间的关系。产品突然说要在首页显示热门评论。顿时,BGM阴沉沉的。这时候,就有点麻烦了。评论模块根本没有留给首页调用评论数据的接口,首页也没有写一段代码调用评论模块的方法获取数据。如果强行沟通,就需要把两个模块的负责人都叫来,坐下来好好谈谈怎么定义接口,怎么调用。通过观察者模式可以方便地扩展功能。代码示例:首先定义观察者,然后定义它的两个基本元素,一个是触发器(fire),另一个是注册(regist)。注册函数会接收两个参数,第一个是要注册的monitor的名称,第二个参数是monitor的操作方法,在函数内部注册monitor;触发器函数接收我们要触发的监视器的名称,然后调用它。有了这个观察者,工程师A和B就不用坐在一起交流了。首页模块只需要触发需要的监听即可,评论模块只需要注册监听即可,无需互相添加。界面。代码示例:代码只是示例,使用观察者时需要newclass。通过观察者模式,两个模块的通信成本和耦合度会变得比较低。3.1.2.转盘应用要求:有一个转盘应用,每转一圈速度变慢。我们先来分析一下步骤:初始化轮播HTML选好奖(抽奖的结果其实是点击抽奖的时候选中的,动画只是为了好看,程序执行时间不会那么长)让carouselrotate=>slowdown=>Turn(cycleuntilstop)然后分析模块:初始化模块(初始化HTML)结果选择模块(选择抽奖结果)动画效果模块(动画)旋转控制模块(控制轮播的旋转和停止转盘)组织通信:初始化模块工作完成后,当点击抽奖按钮时,选择抽奖结果,然后旋转控制模块通知动画模块开始旋转。动画完成后,告诉旋转控制模块,旋转控制模块完成,旋转控制模块通知动画模块下一圈要多快,怎么转,动画模块和转弯模块是一个循环过程。动画模块和旋转模块之间的通信有问题。动画一般是用setInterval完成的,是异步的。异步模块不能直接与同步模块通信。旋转控制模块是同步的,不知道动画模块什么时候结束。一圈,这是一个典型的不方便直接通信的场景,所以这两个模块之间的通信可以使用观察者模式。代码示例:先拿观察者代码,按照分析步骤先写好各个模块。让我们首先指定消息的结构。moveControll模块告诉mover模块如何转,即moveControll会调用mover模块,给它配置。这个配置就是轮换命令。我们同意配置的格式是一个对象。speed属性表示轮换的速度(比如speed为50,表示每50毫秒轮换一个奖品),time属性表示本次轮换多少个奖品(比如时间为10,即本轮转10个奖品)模块架搭建完成后,各模块独立实现。先实现初始化模块,不要写例子的详细代码。我们先定义一个dom数组。整个初始化模块需要做的就是创建10个奖品DOM并压入数组。最后,将这些DOM显示在页面上。因此,选择模块一般会向后台接口请求抽取哪个奖品。在这里,它只是简单地用一个随机数代替。比如我们从0到10中随机化一个随机数,给这个随机数加上40,让它有四个基本圆。转几圈,然后我们将结果四舍五入返回。要实现动画效果模块,我们首先需要定义旋转动画。默认情况下,选择一等奖。轮换时,将二等奖的选择效果加上一等奖的选择效果,依次循环。所以我们定义一个初始下标变量,默认值为0,表示我们当前是一等奖,然后创建一个定时器,执行间隔就是config中的速度,定时器判断当前奖品是否是第一个,然后移除最后一个选中的效果,其他依次类推。看到这段if-else代码中重复的部分,是不是可以想到之前学过的一种设计模式来优化呢?使用享元模式优化代码,先将不同的部分提取为公共共享组件,不同的部分去除的内容不同。我们先在定时器外创建一个变量removeNum,默认为最后一个,即下标为9的物品,然后判断当前奖品是否为第一名。如果不是第一个,将removeNum赋值给nowIn减1,然后设置选中的样式,可以减少重码。以上代码完成了动画效果模块的主要功能。我们还需要一个旋转完成停止的判断。如果在传入的config中判断nowIn等于需要的总轮转次数(时间),则定时器会被清零,轮转会停止。例如config.time等于10,当nowIn等于10时,表示传输结束。轮换完成后,需要通知轮换控制模块轮换已经完成,所以清零定时器后,需要观察者触发一个事件通知轮换控制模块。如果这里不使用观察者模式,异步模块与同步模块的通信会比较困难。通过观察者进行通信会更舒服,代码结构也很清晰。代码勘误:上述动画效果模块的代码示例需要在timer中设置好选中的效果后交给nowIn++。截图的时候忘记更正了。然后我们补充轮换控制模块的代码:先获取抽奖结果赋值给final变量;将旋转总数分配给_circle变量;然后创建变量_runCicle记录跑了多少圈,默认值为0;创建stopNum变量表示最后停止哪个奖品,将抽奖结果分成10得到最后停止哪个奖品;定义初始速度变量_speed,默认值为50;调用mover函数使其第一次旋转,传入旋转圈数和初始速度;然后在observer上注册一个finishmonitor来监听动画效果模块的完成;在monitor的回调中操作,通知动画模块下一次如何转身,首先创建_time变量,初始值为0;转一圈后速度会变慢,所以_speed+=50减慢速度;必须增加已经运行的圈数,所以让_runCircle++;然后判断跑圈数_runCircle是否小于基本圈数_circle,如果是,说明动画模块还需要转10格。如果跑完了,让_time停在最后的奖品处。这样我们就可以很好的组织动画效果模块和旋转控制模块之间的通信。异步模块移动器不知道什么时候完成。直接沟通很不方便,通过观察者很容易解决。3.2.责任链模式示例3.2.1.axios拦截器需求:设计一个axios拦截器axios的拦截器非常简单,采用责任链模式实现。代码示例:首先创建axios类,类中会有一个interceptors属性,其中包含请求拦截对象request和response拦截对象response,这两个对象是interceptorManner类的实例化然后我们创建interceptorManner类,其中有一个use方法,调用这个方法用于添加拦截。use方法接收两个参数,一个拦截成功回调,一个拦截失败回调。我们需要调用use方法将拦截成功回调和拦截失败回调添加到interceptorManner类中。在handler属性中,这个属性是一个数组。整个拦截器执行过程就是不断的把你的拦截操作添加到数组中,形成一个责任链。因为拦截的个数是不确定的,可以添加一个拦截,也可以添加多个拦截,所以通过责任链模式组织代码扩展起来会更容易。当我们发送请求的时候,我们会调用request方法,所以我们在axios类的原型中添加了request方法。请求方法需要将请求拦截器的链、初始链、响应拦截器的链拼接在一起,然后依次执行链。的每一个方法,并让我们的配置在这条链上传递。所以我们定义一个链变量来存放初始链,然后分别遍历请求拦截链和响应拦截链,将这两条链分别放在链的首尾。然后依次执行链,因为链中可能存在同步和异步操作,所以我们可以使用promise来完成。promise.then执行后会返回一个新的Promise对象。在循环中,将每次返回的Promise对象赋值给promise变量,完成自动调用,这样整个链条就会自动执行,然后返回最终结果。.整个axios的源码实现就是一个典型的拦截器操作。通过责任链模式实现代码是不是很清晰?3.2.2.使用责任链模型组织一个表单验证需求:有一个表单,需要前端验证和后端验证。假设我们要验证输入。首先,我们需要绑定输入失去焦点的事件,然后获取要验证的值,我们可能没有想到使用责任链模式写两个方法,然后依次调用它们进行验证,例如:它扩展新功能会很麻烦,所以我们换成责任链模式来实现,代码示例:首先将前端验证和后端验证用数组组织成一个链,然后我们需要执行链中的方法依次。上面已经实现了promise方法,我们将其替换为async方法,在实现async函数中获取待验证的值,然后通过await循环调用链中的方法,最后返回结果。如果这时候需求有变化,我们就需要再增加一个中台验证。只需将方法添加到该位置即可。就像我们前面学习的水果加工厂的例子,如果我们要增加一个消毒环节,只需要增加一个消毒模块即可。责任链的好处就是把我们要做的事情组织成一系列的模块,扩展起来会方便很多。在上面的代码中,你甚至可以把验证操作封装成一个类,然后留下一个接口,其他人可以通过这个接口添加验证操作,这样代码的可扩展性会更高。代码示例:这样组织代码后,如果要添加新的操作,只需要调用add方法把要添加的操作放入即可。3.3.访客模式示例3.3.1。不同角色接入数据需求:公司有很多财务报表,财务报表关心支出和收入,老板关心盈利能力。一家公司的财务报表很多,财务报表的访问者也很多。比如有财务报表和老板等,可能有多个财务报表和多个老板(合资)。他们都想看到他们想看到的东西,这样的情况无论是数据还是访问者都不稳定,访问者模式很适合处理这样的情况。代码示例:先定义财务报表类,有收入、支出、利润属性,然后定义老板类,在老板类中添加get方法获取老板需要的数据,老板关心利润,所以接收一个参数定义财务类,在财务类中添加一个get方法获取财务需要的数据,财务关心收支,所以我们接收两个参数然后我们定义访问者,哪个财务报表和谁正在访问的访客在接收的时候老板和财务这两个参数需要不同的数据,以后可能会有多个角色加入,所以我们创建一个策略工厂来返回对应角色需要的数据。我们还需要知道传入的人是老板还是会计。我们可以通过constructr属性获取调用的构造函数的名字,然后调用对应的角色方法来使用。如果我们有三份财务报表,两个老板,一个财务,老板1想看财务报表2,老板2想看财务报表1,调用访客传入对应的报表和老板就可以了。在这样的代码组织下,我们可以随时更改访问者和访问的财务报表,实现了数据和操作的解耦。下一篇:设计模式——关于如何提高代码质量(远离乱七八糟,人见人爱)本文由博客发布平台OpenWrite发布!
