当前位置: 首页 > 后端技术 > PHP

PHP中的Facadepattern(外观模式)

时间:2023-03-29 15:32:24 PHP

本文来自pilishen.com----原文链接;欢迎加入我们的php&Laravel学习群:109256050本文属于本课程《laravel底层核心概念解析》阅读篇《Laravel底层核心技术实战揭秘》的延伸阅读。考虑到同学们的基础差异,为了避免视频中文章过多,将一些与laravel底层实现相关的PHP知识点以文章的形式呈现出来,供大家预览,随时参考。关于立面一词的翻译,立面一词原指建筑物的表面和外观。它被翻译为建筑学中的“立面”一词。国内对facade这个词的关注可能更多取决于laravel的火爆,似乎一致将laravel中的facade翻译为“门面”。老实说,当我第一次看到翻译文档中提到的“门面”时,我想你和我一样:“你在说什么?你在说商店,商店的门面吗?”直到现在,如果我非要用中文说门面,如果非要用“门面”这个词,我的心还是会不自觉地“咔哒”一声,我知道这里有问题。facade的翻译是什么?但也有一些人干脆提倡不翻译,遇到英文单词直接拿过来就可以了。这不是长久之计。毕竟还是给新人铺垫好理解的好。后来偶然看到台湾的学者,准确的说是台湾的维基百科,把门面图案翻译成“外观图案”。考虑到这个图案的实际作用,我顿时松了口气。即使laravel中的门面严格来说不是门面模式,很多人还在批评laravel对门面这个词的滥用和误导,但它仍然是在借用或模仿门面模式,所以laravel中的门面,这篇文章我也觉得最好翻译成“外观”,当然为了更好理解,可以是“服务外观”。尽管如此,从个人角度来说,我更愿意称其为“服务定位器”、“服务代理”或“服务别名”。一反常态的强硬,暂时不用勉强。通过下面,当你真正了解了什么是门面模式之后,我想你会更好地理解为什么将其翻译为“外观模式”更为合适。什么是facadepattern(“外观模式”的定义),无论是在现实世界还是编程世界中,facade(外观)的目的都是为一个可能丑陋凌乱的事物“披上”美丽诱人的外表,还是面具,用一句中国话来说:什么是容貌?“人靠衣装,马靠鞍”。基于此,门面模式就是将一个或多个杂乱、复杂、难以重构的类添加(或转换)成一个漂亮优雅的接口(interface),这样你会更开心,更方便操作它,从而间接地操作它背后的实际逻辑。何时使用外观模式外观模式(“外观模式”)常用于为一个或多个子系统提供统一的入口界面(interface),或操作界面。当你需要操作别人遗留下来的项目,或者第三方代码的时候。特别是平时,这些代码不容易重构,也没有测试(tests)。此时,可以创建一个门面(“appearance”)来“包装”原始代码,以简化或优化其使用场景。说多了,不如来个直观的例子:例1:在java中,通过门面操作计算机内部的复杂系统信息假设我们有这样一个复杂的子系统逻辑:classCPU{publicvoidfreeze(){...}publicvoidjump(longposition){...}publicvoidexecute(){...}}classMemory{publicvoidload(longposition,byte[]data){...}}classHardDrive{publicbyte[]read(longlba,intsize){...}}为了更方便的操作它们,我们可以创建一个门面类:classComputer{publicvoidstartComputer(){cpu.freeze();memory.load(BOOT_ADDRESS,hardDrive.read(BOOT_SECTOR,SECTOR_SIZE));cpu.jump(BOOT_ADDRESS);cpu.execute();}}然后我们的客户可以很容易地这样调用:classYou{publicstaticvoidmain(String[]args){Computerfacade=newComputer();facade.startComputer();}}例子2:一个糟糕的第三方邮件类假设你要用到下面这个看起来很糟糕的第三方邮件类,尤其是里面的各个方法名,你得多停留几秒才能看懂:interfaceSendMailInterface{公共功能setSendToEmailAddress($emailAddress);公共函数setSubjectName($subject);公共函数setTheEma内容($正文);公共功能setTheHeaders($headers);公共函数getTheHeaders();公共函数getTheHeadersText();公共函数sendTheEmailNow();}类SendMail实现SendMailInterface{public$to,$subject,$body;公共$headers=array();公共功能setSendToEmailAddress($emailAddress){$this->to=$emailAddress;}publicfunctionsetSubjectName($subject){$this->subject=$subject;}publicfunctionsetTheEmailContents($body){$this->body=$body;}publicfunctionsetTheHeaders($headers){$this->headers=$headers;}publicfunctiongetTheHeaders(){return$this->headers;}publicfunctiongetTheHeadersText(){$headers="";foreach($this->getTheHeaders()as$header){$headers.=$header."\r\n";}}公共函数sendTheEmailNow(){邮件($this->to,$this->subject,$this->body,$this->getTheHeadersText());}}这个时候你直接去改源码就不太容易了。没办法,我们创建一个门面类SendMailFacade{private$sendMail;公共函数__construct(SendMailInterface$sendMail){$this->sendMail=$sendMail;}publicfunctionsetTo($to){$this->sendMail->setSendToEmailAddress($to);返回$这个;}publicfunctionsetSubject($subject){$this->sendMail->setSubjectName($subject);返回$这个;}publicfunctionsetBody($body){$this->sendMail->setTheEmailContents($body);返回$这个;}publicfunctionsetHeaders($headers){$this->sendMail->setTheHeaders($headers);返回$这个;}publicfunctionsend(){$this->sendMail->sendTheEmailNow();那么原始的一个未优化的终端调用可能看起来像这样:$sendMail=newSendMail();$sendMail->setSendToEmailAddress($to);$sendMail->setSubjectName($subject);$sendMail->setTheEmailContents($body);$sendMail->setTheHeaders($headers);$sendMail->sendTheEmailNow();现在您有了外观类,您可以这样做:$sendMail=newSendMail();$sendMailFacade=newsendMailFacade($sendMail);$sendMailFacade->setTo($to)->setSubject($subject)->setBody($body)->setHeaders($headers)->send();示例3:完成一个复杂的商品交易流程假设一个商品交易环节需要有这么几步:$productID=$_GET['productId'];$qtyCheck=newproductQty();//检查库存if($qtyCheck->checkQty($productID)>0){//添加商品到购物车$addToCart=newaddToCart($productID);//计算运费$shipping=newshippingCharge();$运费->更新费用();//计算折扣$discount=newdiscount();$discount->applyDiscount();$订单=新订单();$order->generateOrder();}可以看到一个流程包含很多步骤,涉及很多对象。一旦在多个地方使用相似的链接,可能会出现问题,所以可以先创建一个外观类:classproductOrderFacade{public$productID='';公共职能__construct($pID){$this->productID=$pID;}publicfunctiongenerateOrder(){if($this->qtyCheck()){$this->addToCart();$this->calulateShipping();$this->applyDiscount();$this->placeOrder();}}privatefunctionaddToCart(){/*..将产品添加到购物车..*/}privatefunctionqtyCheck(){$qty='从数据库获取产品数量';如果($qty>0){返回真;}else{返回真;}}私有函数calulateShipping(){$shipping=newshippingCharge();$shipping->calculateCharge();}privatefunctionapplyDiscount(){$discount=newdiscount();$discount->applyDiscount();}私有函数placeOrder(){$order=neworder();$order->generateOrder();这样我们的终端调用就可以两行解决:$order=newproductOrderFacade($productID);$order->generateOrder();示例4:向多个社交媒体同步消息的过程//发送Twitter消息classCodeTwit{functiontweet($status,$url){var_dump('Tweeted:'.$status.'from:'.$url);}}//在Googleplus上分享classGooglize{functionshare($url){var_dump('SharedonGoogleplus:'.$url);}}//在Reddit上分享classReddiator{functionreddit($url,$title){var_dump('Reddit!url:'.$url.'title:'.$title);}}如果我们每次都写一篇文章,想转发到其他平台,我们不得不分别调用相应的方法,工作量太大,而且后期平台数量往往只增不减。这次借助外观类:classshareFacade{protected$twitter;保护$谷歌;受保护的$reddit;函数__construct($twitterObj,$gooleObj,$redditObj){$this->twitter=$twitterObj;$this->google=$gooleObj;$this->reddit=$redditObj;}functionshare($url,$title,$status){$this->twitter->tweet($status,$url);$this->google->share($url);$this->reddit->reddit($url,$title);}}这样,终端调用可以是:$shareObj=newshareFacade($twitterObj,$gooleObj,$redditObj);$shareObj->share('//myBlog.com/post-awsome','Mygreatestpost','阅读我有史以来最棒的帖子。');外观模式的优点和缺点可以将你的终端调用与其背后的子系统逻辑解耦,这经常发生在你的控制器中,这意味着你的控制器可以有更少的依赖,控制器关注的更少,所以职责和逻辑是更清晰,也意味着你子系统中的逻辑发生变化,不会影响你控制器中的终端调用。虽然这个缺点特别有用,但一个常见的陷阱是过度使用这种模式。那时你可能不需要它,所以你只需要注意它。当然,有人反驳说我的原码可以用,何必呢,那也是房子,你是喜欢住精致的房子,还是只有四面墙?门面模式是否与其他设计模式相似?认真学过我们《Laravel底层核心技术实战揭秘》课程的同学可能会觉得以前见过这个门面图案。也许你会脱口而出:“这个产品和我们之前学的装饰者模式有什么区别?为什么不直接说是装饰者模式呢?”的确,在“包装”逻辑上,它们确实相似,但是:装饰器模式(Decorator)——用于在不改变原有代码外观的情况下,为一个对象添加和包装新的行为和逻辑外观模式(facadepattern)——用于提供一个或多个复杂的子系统,或者第三方库,提供统一入口,或者统一终端调用方式还是有些区别的~参考文章://zh.wikipedia.org/wiki/%E5%A4%96%E8%A7%80%E6%A8%A1%E5%BC%8F//www.jakowicz.com/facade-pattern-in-php///code.tutsplus.com/tutorials/design-patterns-the-facade-pattern--cms-22238//phpenthusiast.com/blog/simplify-your-php-code-with-facade-class