SOLID是MichaelFeathers推荐的首字母缩写词,方便记忆。它代表了罗伯特·马丁命名的最重要的五个面向对象编码设计原则S:单一职责原则(SRP)O:开闭原则(OCP)L:里氏代换原则(LSP)I:接口隔离原则(ISP)D:依赖倒置原则(DIP)单一职责原则(SRP)“修改一个类应该只出于一个原因”。人们总是容易把一个类塞满一堆方法,就像我们只能带着一个行李箱上飞机一样(把所有东西都塞进行李箱)。这样做的问题是,从概念上讲,这样一个类的内聚性不高,并且有很多修改它的理由。尽量减少需要修改类的次数很重要。这是因为,当一个类中有很多方法时,很难知道修改其中一个会影响代码库中的哪些依赖模块。错误:类UserSettings{private$user;公共函数__construct($user){$this->user=$user;}publicfunctionchangeSettings($settings){if($this->verifyCredentials()){//..}}privatefunctionverifyCredentials(){//...}}Good:classUserAuth{private$user;公共函数__construct($user){$this->user=$user;}publicfunctionverifyCredentials(){//...}}classUserSettings{private$user;私人$授权;公共函数__construct($user){$this->user=$user;$this->auth=newUserAuth($user);}publicfunctionchangeSettings($settings){if($this->auth->verifyCredentials()){//...}}}开放/封闭原则(OCP)正如BertrandMeyer所说,“软件实体(类,模块、功能等)应该对扩展开放,对修改关闭。”该原则指出,应允许用户在不更改现有代码的情况下添加新功能。错误:抽象类适配器{protected$name;publicfunctiongetName(){return$this->name;}}classAjaxAdapterextendsAdapter{publicfunction__construct(){parent::__construct();$this->name='ajaxAdapter';}}classNodeAdapterextendsAdapter{publicfunction__construct(){parent::__construct();$this->name='nodeAdapter';}}类HttpRequester{私有$adapter;公共函数__construct($adapter){$this->adapter=$adapter;}publicfunctionfetch($url){$adapterName=$this->adapter->getName();如果($adapterName==='ajaxAdapter'){return$this->makeAjaxCall($url);}elseif($adapterName==='httpNodeAdapter'){return$this->makeHttpCall($url);}}privatefunctionmakeAjaxCall($url){//请求并返回promise}privatefunctionmakeHttpCall($url){//requestandreturnpromise}}在上面的代码中,对于HttpRequester类中的fetch方法,如果我新增一个xxxAdapter类并在fetch方法中使用,我需要修改HttpRequester类(比如加一个elseif判断),通过下面的代码,可以很好的解决这个问题。以下代码很好地说明了如何在不更改原始代码的情况下添加新功能。Good:interfaceAdapter{publicfunctionrequest($url);}classAjaxAdapterimplementsAdapter{publicfunctionrequest($url){//请求并返回promise}}classNodeAdapterimplementsAdapter{publicfunctionrequest($url){//请求并返回承诺}}classHttpRequester{private$adapter;公共函数__construct(Adapter$adapter){$this->adapter=$adapter;}publicfunctionfetch($url){return$this->adapter->request($url);}}里氏代换原则(LSP)最好地解释了这个概念:如果你有一个父类和一个子类,父类和子类不会改变原来结果的正确性。子类是可以互换的。这听起来令人困惑,所以让我们看一个经典的正方形-矩形示例。从数学上讲,正方形是一种矩形,但是当您的模型通过继承使用“is-a”关系时,这是不对的。错误:类矩形{protected$width=0;保护$height=0;publicfunctionrender($area){//...}publicfunctionsetWidth($width){$this->width=$width;}publicfunctionsetHeight($height){$this->height=$height;}publicfunctiongetArea(){return$this->width*$this->height;}}classSquareextendsRectangle{publicfunctionsetWidth($width){$this->width=$this->height=$width;}publicfunctionsetHeight(height){$this->width=$this->height=$height;}}functionrenderLargeRectangles($rectangles){foreach($rectanglesas$rectangle){$rectangle->setWidth(4);$rectangle->setHeight(5);$area=$rectangle->getArea();//错误:将为Square返回25。应该是20。$rectangle->render($area);}}$rectangles=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles($rectangles);Good:抽象类Shape{protected$width=0;保护$height=0;抽象公共函数getArea();publicfunctionrender($area){//...}}classRectangleextendsShape{publicfunctionsetWidth($width){$this->width=$width;}publicfunctionsetHeight($height){$this->height=$height;}publicfunctiongetArea(){return$this->width*$this->height;}}类Square扩展Shape{private$length=0;publicfunctionsetLength($length){$this->length=$length;}publicfunctiongetArea(){returnpow($this->length,2);}}functionrenderLargeRectangles($rectangles){foreach($rectanglesas$rectangle){if($rectangleinstanceofSquare){$rectangle->setLength(5);}elseif($rectangleinstanceofRectangle){$rectangle->setWid第(4);$rectangle->setHeight(5);$area=$rectangle->getArea();$rectangle->render($area);}}$shapes=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles($shapes);InterfaceSegregationPrinciple接口隔离原则:“不应强迫客户端实现它不需要的接口。”有一个清楚的例子可以证明这个原则。当一个类需要大量设置时,不要求客户设置大量选项是很方便的,因为通常他们不需要所有设置。使设置可选有助于我们避免“胖接口”Bad:interfaceEmployee{publicfunctionwork();publicfunctioneat();}classHumanimplementsEmployee{publicfunctionwork(){//....working}publicfunctioneat(){//......在午休时间吃东西}}classRobotimplementsEmployee{publicfunctionwork(){//....工作更多}publicfunctioneat(){//....机器人不能吃,但它必须实现这个方法}}在上面的代码中,机器人类不需要eat()方法,但是实现了Emplyee接口,所以只需要实现所有的方法即可。这使得Robot实现它不需要的方法。所以这里应该拆分出Emplyee接口。正确的代码如下:Good:interfaceWorkable{publicfunctionwork();}interfaceFeedable{publicfunctioneat();}interfaceEmployeeextendsFeedable,Workable{}classHumanimplementsEmployee{publicfunctionwork(){//....working}publicfunctioneat(){//....在午休时间吃饭}}//robotcanonlyworkclassRobotimplementsWorkable{publicfunctionwork(){//...working}}DependencyInversion原理依赖倒置原理(DIP)这个原理说明了两个基本点:高层模块不应该依赖低层模块,它们都应该依赖抽象。Abstractionshouldnotdependonimplementationshoulddependontheabstraction乍一看似乎有点神秘,但如果你使用过PHP框架(如Symfony),你应该已经看到DependencyInjection(DI)实现这个概念.尽管它们不是完全相互关联的概念,但依赖倒置原则将高级模块的实现细节和创建与低级模块分开。这可以使用依赖注入(DI)来实现。另一个好处是它解耦了模块。耦合让你很难重构,这是一种非常糟糕的开发模式。错误:classEmployee{publicfunctionwork(){//....working}}classRobotextendsEmployee{publicfunctionwork(){//....workingmuchmore}}classManager{private$employee;公共函数__construct(Employee$employee){$this->employee=$employee;}publicfunctionmanage(){$this->employee->work();}}Good:interfaceEmployee{publicfunctionwork();}classHumanimplementsEmployee{publicfunctionwork(){//....working}}classRobotimplementsEmployee{publicfunctionwork(){//....workmuchmore}}classManager{private$employee;公共函数__construct(Employee$employee){$this->employee=$employee;}publicfunctionmanage(){$this->employee->work();}}不要写重复代码(DRY)这个原则大家应该比较熟悉了。尽量避免重复代码,这是一种非常糟糕的做法,重复代码通常意味着当您需要更改某些逻辑时,您需要修改多个地方。错误:functionshowDeveloperList($developers){foreach($developersas$developer){$expectedSalary=$developer->calculateExpectedSalary();$experience=$developer->getExperience();$githubLink=$developer->getGithubLink();$data=[$expectedSalary,$experience,$githubLink];渲染($数据);}}functionshowManagerList($managers){foreach($managersas$manager){$expectedSalary=$manager->calculateExpectedSalary();$experience=$manager->getExperience();$githubLink=$manager->getGithubLink();$data=[$expectedSalary,$experience,$githubLink];渲染($数据);}}Good:functionshowList($employees){foreach($employeesas$employee){$expectedSalary=$employee->calculateExpectedSalary();$experience=$employee->getExperience();$克ithubLink=$employee->getGithubLink();$data=[$expectedSalary,$experience,$githubLink];渲染($数据);}}很好:functionshowList($employees){foreach($employeesas$employee){render([$employee->calculateExpectedSalary(),$employee->getExperience(),$employee->getGithubLink()]);}}后记:虽然OOP设计需要遵守以上原则,但是实际的代码设计一定要简单,简单,简单在实际编码中,你要根据情况做出取舍。如果一味地遵守原则而不注意实际情况,你的代码可能会变得难以理解!更多好文,关注公众号获取
