JavaScript 设计模式 – 通过示例进行解释_第1页
JavaScript 设计模式 – 通过示例进行解释_第2页
JavaScript 设计模式 – 通过示例进行解释_第3页
JavaScript 设计模式 – 通过示例进行解释_第4页
JavaScript 设计模式 – 通过示例进行解释_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

​大家好!在本文中,我将解释什么是设计模式以及它们为何有用。目录什么是设计模式?创意设计模式单例模式工厂方法模式抽象工厂模式建造者模式原型模式结构设计模式适配器模式装饰模式立面图案代理模式行为设计模式责任链模式迭代器模式观察者模式什么是设计模式?设计模式因《设计模式:可重用面向对象软件的元素》一书而流行起来,该书由四位C++工程师于1994年出版。本书探讨了面向对象编程的功能和陷阱,并描述了23种有用的模式,您可以实现这些模式来解决常见的编程问题。这些模式不是算法或特定的实现。它们更像是想法、观点和抽象概念,在某些情况下可用于解决特定类型的问题。模式的具体实现可能会根据许多不同的因素而有所不同。但重要的是它们背后的概念,以及它们如何帮助我们为我们的问题找到更好的解决方案。话虽这么说,请记住这些模式是在考虑OOPC++编程的情况下想到的。当涉及更现代的语言(如JavaScript或其他编程范例)时,这些模式可能不会同样有用,甚至可能会向我们的代码添加不必要的样板文件。尽管如此,我认为了解它们作为一般编程知识还是有好处的。旁注:如果您不熟悉编程范例或OOP,我最近写了两篇关于这些主题的文章。无论如何...现在我们已经完成了介绍,设计模式分为三个主要类别:创建模式、结构模式和行为模式。让我们简要探讨一下它们。创意设计模式创建模式由用于创建对象的不同机制组成。单例模式Singleton是一种设计模式,可确保一个类只有一个不可变的实例。简单地说,单例模式由一个无法复制或修改的对象组成。当我们想要为我们的应用程序提供一些不可变的单点事实时,它通常很有用。举例来说,我们希望将应用程序的所有配置都放在一个对象中。我们希望禁止对该对象进行任何复制或修改。实现此模式的两种方法是使用对象文字和类:constConfig={

start:()=>console.log('Apphasstarted'),

update:()=>console.log('Apphasupdated'),}//WefreezetheobjecttopreventnewpropertiesbeingaddedandexistingpropertiesbeingmodifiedorremovedObject.freeze(Config)Config.start()//"Apphasstarted"Config.update()//"Apphasupdated"C="Robert"//Wetrytoaddanewkeyconsole.log(Config)//Andverifyitdoesn'twork:{start:[Function:start],update:[Function:update]}使用对象字面量classConfig{

constructor(){}

start(){console.log('Apphasstarted')}

update(){console.log('Apphasupdated')}}

constinstance=newConfig()Object.freeze(instance)使用类工厂方法模式工厂方法模式提供了一个用于创建对象的接口,这些对象在创建后可以进行修改。最酷的一点是,创建对象的逻辑集中在一个地方,从而简化并更好地组织我们的代码。这种模式被大量使用,也可以通过两种不同的方式实现,通过类或工厂函数(返回对象的函数)。classAlien{

constructor(name,phrase){

=name

this.phrase=phrase

this.species="alien"

}

fly=()=>console.log("Zzzzzziiiiiinnnnnggggg!!")

sayPhrase=()=>console.log(this.phrase)}constalien1=newAlien("Ali","I'mAlithealien!")console.log()//output:"Ali"使用类functionAlien(name,phrase){

=name

this.phrase=phrase

this.species="alien"}Atotype.fly=()=>console.log("Zzzzzziiiiiinnnnnggggg!!")Atotype.sayPhrase=()=>console.log(this.phrase)constalien1=newAlien("Ali","I'mAlithealien!")console.log()//output"Ali"console.log(alien1.phrase)//output"I'mAlithealien!"alien1.fly()//output"Zzzzzziiiiiinnnnnggggg"使用工厂函数抽象工厂模式抽象工厂模式允许我们在不指定具体类的情况下生成相关对象系列。当我们需要创建仅共享某些属性和方法的对象时,它非常有用。它的工作方式是通过呈现一个与客户端交互的抽象工厂。该抽象工厂根据相应的逻辑调用相应的具体工厂。该具体工厂就是返回最终对象的工厂。基本上,它只是在工厂方法模式上添加了一个抽象层,以便我们可以创建许多不同类型的对象,但仍然与单个工厂函数或类交互。让我们通过一个例子来看看。假设我们正在为一家汽车公司建模一个系统,该公司当然生产汽车,但也生产摩托车和卡车。//Wehaveaclassor"concretefactory"foreachvehicletypeclassCar{

constructor(){

="Car"

this.wheels=4

}

turnOn=()=>console.log("Chacabúm!!")}classTruck{

constructor(){

="Truck"

this.wheels=8

}

turnOn=()=>console.log("RRRRRRRRUUUUUUUUUMMMMMMMMMM!!")}classMotorcycle{

constructor(){

="Motorcycle"

this.wheels=2

}

turnOn=()=>console.log("sssssssssssssssssssssssssssssshhhhhhhhhhham!!")}//Andandabstractfactorythatworksasasinglepointofinteractionforourclients//Giventhetypeparameteritreceives,itwillcallthecorrespondingconcretefactoryconstvehicleFactory={

createVehicle:function(type){

switch(type){

case"car":

returnnewCar()

case"truck":

returnnewTruck()

case"motorcycle":

returnnewMotorcycle()

default:

returnnull

}

}}constcar=vehicleFactory.createVehicle("car")//Car{turnOn:[Function:turnOn],name:'Car',wheels:4}consttruck=vehicleFactory.createVehicle("truck")//Truck{turnOn:[Function:turnOn],name:'Truck',wheels:8}constmotorcycle=vehicleFactory.createVehicle("motorcycle")//Motorcycle{turnOn:[Function:turnOn],name:'Motorcycle',wheels:2}建造者模式Builder模式用于按“步骤”创建对象。通常我们会有函数或方法向我们的对象添加某些属性或方法。这种模式最酷的一点是我们将属性和方法的创建分离到不同的实体中。如果我们有一个类或工厂函数,我们实例化的对象将始终具有该类/工厂中声明的所有属性和方法。但是使用构建器模式,我们可以创建一个对象并仅应用我们需要的“步骤”,这是一种更灵活的方法。这与对象组合有关,这是我在这里讨论的主题。//Wedeclareourobjectsconstbug1={

name:"BuggyMcFly",

phrase:"Yourdebuggerdoesn'tworkwithme!"}constbug2={

name:"MartinianoBuggland",

phrase:"Can'ttouchthis!Nananana..."}//ThesefunctionstakeanobjectasparameterandaddamethodtothemconstaddFlyingAbility=obj=>{

obj.fly=()=>console.log(`Now${}canfly!`)}constaddSpeechAbility=obj=>{

obj.saySmthg=()=>console.log(`${}walksthewalkandtalksthetalk!`)}//FinallywecallthebuilderfunctionspassingtheobjectsasparametersaddFlyingAbility(bug1)bug1.fly()//output:"NowBuggyMcFlycanfly!"addSpeechAbility(bug2)bug2.saySmthg()//output:"MartinianoBugglandwalksthewalkandtalksthetalk!"原型模式原型模式允许您使用另一个对象作为蓝图来创建一个对象,并继承其属性和方法。如果您使用JavaScript一段时间,您可能熟悉原型继承以及JavaScript的工作原理。最终结果与我们使用类获得的结果非常相似,但具有更多的灵活性,因为属性和方法可以在对象之间共享,而不依赖于同一个类。//Wedeclareourprototypeobjectwithtwomethodsconstenemy={

attack:()=>console.log("PimPamPum!"),

flyAway:()=>console.log("Flyyyylikeaneagle!")}//Wedeclareanotherobjectthatwillinheritfromourprototypeconstbug1={

name:"BuggyMcFly",

phrase:"Yourdebuggerdoesn'tworkwithme!"}//WithsetPrototypeOfwesettheprototypeofourobjectObject.setPrototypeOf(bug1,enemy)//WithgetPrototypeOfwereadtheprototypeandconfirmtheprevioushasworkedconsole.log(Object.getPrototypeOf(bug1))//{attack:[Function:attack],flyAway:[Function:flyAway]}console.log(bug1.phrase)//Yourdebuggerdoesn'tworkwithme!console.log(bug1.attack())//PimPamPum!console.log(bug1.flyAway())//Flyyyylikeaneagle!结构设计模式结构模式是指如何将对象和类组装成更大的结构。适配器模式适配器允许两个具有不兼容

接口的对象相互交互。例如,假设您的应用程序查询一个返回XML的API,并将该信息发送到另一个API来处理该信息。但处理API需要JSON。由于两个接口不兼容,因此您无法发送收到的信息。你需要先适应它。我们可以用一个更简单的例子来形象化相同的概念。假设我们有一系列城市和一个返回这些城市拥有的最大居民数量的函数。我们数组中的居民数量以百万为单位,但我们要添加一个新城市,其居民没有百万转换://OurarrayofcitiesconstcitiesHabitantsInMillions=[

{city:"London",habitants:8.9},

{city:"Rome",habitants:2.8},

{city:"Newyork",habitants:8.8},

{city:"Paris",habitants:2.1},]//ThenewcitywewanttoaddconstBuenosAires={

city:"BuenosAires",

habitants:3100000}//OuradapterfunctiontakesourcityandconvertsthehabitantspropertytothesameformatalltheothercitieshaveconsttoMillionsAdapter=city=>{city.habitants=parseFloat((city.habitants/1000000).toFixed(1))}toMillionsAdapter(BuenosAires)//WeaddthenewcitytothearraycitiesHabitantsInMillions.push(BuenosAires)//AndthisfunctionreturnsthelargesthabitantsnumberconstMostHabitantsInMillions=()=>{

returnMath.max(...citiesHabitantsInMillions.map(city=>city.habitants))}console.log(MostHabitantsInMillions())//8.9装饰模式装饰器模式允许您通过将新行为放置在包含行为的包装对象内来将新行为附加到对象上。如果您对React和高阶组件(HOC)有一定的了解,这种方法可能会让您感到熟悉。从技术上讲,React中的组件是函数,而不是对象。但是,如果我们考虑一下ReactContext或Memo的方式,我们可以看到我们将一个组件作为子组件传递给这个HOC,并且由于这个子组件能够访问某些功能。在此示例中,我们可以看到ContextProvider组件正在接收子项作为props:import{useState}from'react'importContextfrom'./Context'constContextProvider:React.FC=({children})=>{

const[darkModeOn,setDarkModeOn]=useState(true)

const[englishLanguage,setEnglishLanguage]=useState(true)

return(

<Context.Providervalue={{

darkModeOn,

setDarkModeOn,

englishLanguage,

setEnglishLanguage

}}>

{children}

</Context.Provider>

)}exportdefaultContextProvider然后我们将整个应用程序围绕它:exportdefaultfunctionApp(){

return(

<ContextProvider>

<Router>

<ErrorBoundary>

<Suspensefallback={<></>}>

<Header/>

</Suspense>

<Routes>

<Routepath='/'element={<Suspensefallback={<></>}><AboutPage/></Suspense>}/>

<Routepath='/projects'element={<Suspensefallback={<></>}><ProjectsPage/></Suspense>}/>

<Routepath='/projects/helpr'element={<Suspensefallback={<></>}><HelprProject/></Suspense>}/>

<Routepath='/projects/myWebsite'element={<Suspensefallback={<></>}><MyWebsiteProject/></Suspense>}/>

<Routepath='/projects/mixr'element={<Suspensefallback={<></>}><MixrProject/></Suspense>}/>

<Routepath='/projects/shortr'element={<Suspensefallback={<></>}><ShortrProject/></Suspense>}/>

<Routepath='/curriculum'element={<Suspensefallback={<></>}><CurriculumPage/></Suspense>}/>

<Routepath='/blog'element={<Suspensefallback={<></>}><BlogPage/></Suspense>}/>

<Routepath='/contact'element={<Suspensefallback={<></>}><ContactPage/></Suspense>}/>

</Routes>

</ErrorBoundary>

</Router>

</ContextProvider>

)}之后,使用该useContext钩子,我可以从应用程序中的任何组件访问上下文中定义的状态。constAboutPage:React.FC=()=>{

const{darkModeOn,englishLanguage}=useContext(Context)

return(...)}exportdefaultAboutPage同样,这可能不是本书作者在撰写此模式时所想到的确切实现,但我相信想法是相同的。将一个对象放置在另一个对象中,以便它可以访问某些功能。;)立面图案外观模式为库、框架或任何其他复杂的类集提供了简化的接口。嗯……我们可能可以为此举出很多例子,对吗?我的意思是,React本身或任何无数的库几乎用于与软件开发相关的任何内容。特别是当我们考虑声明式编程时,一切都是为了提供抽象,从而隐藏开发人员眼中的复杂性。一个简单的例子就是JavaScript的map、sort、reduce和filter函数,它们的工作原理就像for底层的良好循环一样。另一个例子可以是当今用于UI开发的任何库,例如MUI。正如我们在下面的示例中看到的,这些库为我们提供了带有内置特性和功能的组件,可以帮助我们更快、更轻松地构建代码。但所有这些在编译后都会变成简单的HTML元素,这是浏览器唯一能理解的东西。这些组件只是抽象概念,旨在让我们的生活更轻松。import*asReactfrom'react';importTablefrom'@mui/material/Table';importTableBodyfrom'@mui/material/TableBody';importTableCellfrom'@mui/material/TableCell';importTableContainerfrom'@mui/material/TableContainer';importTableHeadfrom'@mui/material/TableHead';importTableRowfrom'@mui/material/TableRow';importPaperfrom'@mui/material/Paper';functioncreateData(

name:string,

calories:number,

fat:number,

carbs:number,

protein:number,){

return{name,calories,fat,carbs,protein};}constrows=[

createData('Frozenyoghurt',159,6.0,24,4.0),

createData('Icecreamsandwich',237,9.0,37,4.3),

createData('Eclair',262,16.0,24,6.0),

createData('Cupcake',305,3.7,67,4.3),

createData('Gingerbread',356,16.0,49,3.9),];exportdefaultfunctionBasicTable(){

return(

<TableContainercomponent={Paper}>

<Tablesx={{minWidth:650}}aria-label="simpletable">

<TableHead>

<TableRow>

<TableCell>Dessert(100gserving)</TableCell>

<TableCellalign="right">Calories</TableCell>

<TableCellalign="right">Fat (g)</TableCell>

<TableCellalign="right">Carbs (g)</TableCell>

<TableCellalign="right">Protein (g)</TableCell>

</TableRow>

</TableHead>

<TableBody>

{rows.map((row)=>(

<TableRow

key={}

sx={{'&:last-childtd,&:last-childth':{border:0}}}

>

<TableCellcomponent="th"scope="row">

{}

</TableCell>

<TableCellalign="right">{row.calories}</TableCell>

<TableCellalign="right">{row.fat}</TableCell>

<TableCellalign="right">{row.carbs}</TableCell>

<TableCellalign="right">{tein}</TableCell>

</TableRow>

))}

</TableBody>

</Table>

</TableContainer>

);}代理模式代理模式为另一个对象提供替代或占位符。这个想法是控制对原始对象的访问,在请求到达实际原始对象之前或之后执行某种操作。再说一遍,如果您熟悉ExpressJS,这可能会让您感到熟悉。Express是一个用于开发NodeJSAPI的框架,它的特点之一就是使用中间件。中间件只不过是我们可以在任何请求到达端点之前、中间或之后执行的代码片段。让我们看一个例子。这里我有一个验证身份验证令牌的函数。不要太关注它是如何做到这一点的。只需知道它接收令牌作为参数,一旦完成,它就会调用该next()函数。constjwt=require('jsonwebtoken')module.exports=functionauthenticateToken(req,res,next){

constauthHeader=req.headers['authorization']

consttoken=authHeader&&authHeader.split('')[1]

if(token===null)returnres.status(401).send(JSON.stringify('Noaccesstokenprovided'))

jwt.verify(token,process.env.TOKEN_SECRET,(err,user)=>{

if(err)returnres.status(403).send(JSON.stringify('Wrongtokenprovided'))

req.user=user

next()

})}该函数是一个中间件,我们可以通过以下方式在API的任何端点中使用它。我们只需将中间件放置在端点地址之后和端点函数声明之前:router.get('/:jobRecordId',authenticateToken,async(req,res)=>{

try{

constjob=awaitJobRecord.findOne({_id:req.params.jobRecordId})

res.status(200)

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论