SOLID是MichaelFeathers为了便于记忆推荐的首字母缩写词。它代表了罗伯特·马丁命名的五个最重要的面向对象编码设计原则:S:单一职责原则(SRP)O:开闭原则(OCP)L:里氏代换原则(LSP)I:接口隔离原则(ISP)D:DependencyInversionPrinciple(DIP)SingleResponsibilityPrinciple(SRP)“ModifyingaclassshouldJustforonereason”。人们总是容易把一个类塞满一堆方法,就像我们只能带着一个行李箱上飞机一样(把所有东西都塞进行李箱)。这样做的问题是,从概念上讲,这样一个类的内聚性不高,并且有很多修改它的理由。尽量减少需要修改类的次数很重要。这是因为,当一个类中有很多方法时,很难知道修改其中一个会影响代码库中的哪些依赖模块。错误:classUserSettings{private$user;publicfunction__construct($user){$this->user=$user;}publicfunctionchangeSettings($settings){if($this->verifyCredentials()){//...}}privatefunctionverifyCredentials(){//...}}好:classUserAuth{private$user;publicfunction__construct($user){$this->user=$user;}publicfunctionverifyCredentials(){//...}}classUserSettings{private$user;private$auth;publicfunction__construct($user){$this->user=$user;$this->auth=newUserAuth($user);}publicfunctionchangeSettings($settings){if($this->auth->verifyCredentials()){//...}}}开放/封闭原则(OCP)正如BertrandMeyer所说,“软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。”这个原则是应该允许用户在不改变现有代码的情况下添加新的功能。坏:abstractclassAdapter{protected$name;publicfunctiongetName(){return$this->name;}}classAjaxAdapterextendsAdapter{publicfunction__construct(){parent::__construct();$this->name='ajaxAdapter';}}classNodeAdapterextendsAdapter{publicfunction__construct(){parent::__construct();$this->name='nodeAdapter';}}classHttpRequester{private$adapter;publicfunction__construct($adapter){$this->adapter=$adapter;}publicfunctionfetch($url){$adapterName=$this->adapter->getName();if($adapterName==='ajaxAdapter'){return$this->makeAjaxCall($url);}elseif($adapterName==='httpNodeAdapter'){return$this->makeHttpCall($url);}}privatefunctionmakeAjaxCall($url){//requestandreturnpromise}privatefunctionmakeHttpCall($url){//requestandreturnpromise}}上面代码中,对于HttpRequester类中的fetch方法,如果我new如果在fetch方法中新增并使用了xxxAdapter类,则需要修改HttpRequester类中的类(比如添加elseif判断),如下代码即可很喜欢这个问题。以下代码很好地说明了如何在不更改原始代码的情况下添加新功能。好的:interfaceAdapter{publicfunctionrequest($url);}classAjaxAdapterimplementsAdapter{publicfunctionrequest($url){//requestandreturnpromise}}classNodeAdapterimplementsAdapter{publicfunctionrequest($url){//requestandreturnpromise}}classHttpRequester{private$adapter;publicfunction__construct(Adapter$adapter){$this->adapter=$adapter;}publicfunctionfetch($url){return$this->adapter->request($url);}}LiskovSubstitutionPrinciple(LSP)这个概念的最佳解释是:如果你有一个超类和一个子类,超类和子类可以互换而不改变原始结果的正确性。这听起来令人困惑,所以让我们看一个经典的正方形-矩形示例。从数学上讲,正方形是一种矩形,但是当您的模型通过继承使用“is-a”关系时,这是不对的。错误:classRectangle{protected$width=0;protected$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=$宽度;}publicfunctionsetHeight(高度){$this->width=$this->height=$height;}}functionrenderLargeRectangles($rectangles){foreach($rectanglesas$rectangle){$rectangle->setWidth(4);$rectangle->setHeight(5);$area=$rectangle->getArea();//错误:Willreturn25forSquare.Shouldbe20.$rectangle->render($area);}}$rectangles=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles($rectangles);Good:abstractclassShape{protected$width=0;protected$height=0;abstractpublicfunctiongetArea();publicfunctionrender($area){//...}}classRectangleextendsShape{publicfunctionsetWidth($width)){$这个->width=$width;}publicfunctionsetHeight($height){$this->height=$height;}publicfunctiongetArea(){return$this->width*$this->height;}}classSquareextendsShape{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->setWidth(4);$rectangle->setHeight(5);}$area=$rectangle->getArea();$rectangle->render($area);}}$shapes=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles($shapes);InterfaceIsolationPrinciple接口隔离原则:“不应强迫客户端实现它不需要的接口”有一个清晰的例子可以证明这一原则。当一个类需要大量设置时,不要求客户设置大量选项是很方便的,因为通常他们不需要所有设置。将设置设为可选有助于我们避免“胖接口”}}classRobotimplementsEmployee{publicfunctionwork(){//...workingmuchmore}publicfunctioneat(){//...robotcan'teat,butitmustmplementthismethod}}上面代码中,Robot类不需要eat()方法,而是实现Emplyee接口,所以它只能实现所有的方法,这使得Robot实现了它不需要的方法。所以这里应该拆分Emplyee接口。正确的代码如下:Good:interfaceWorkable{publicfunctionwork();}interfaceFeedable{publicfunctioneat();}interfaceEmployeeextendsFeedable,Workable{}classHumanimplementsEmployee{publicfunctionwork(){//....working}publicfunctioneat(){//...eatinginlunchbreak}}//robotcanonlyworkclassRobotimplementsWorkable{publicfunctionwork(){//...working}}DependencyInversionPrincipleDependencyInversionPrinciple(DIP)这个原则说明了两个基本点:高层模块不应该依赖低层模块,他们shouldalldependonabstractionsAbstractionsshouldnotdependonimplementations,implementationsshoulddependonabstractions这行乍一看可能有点神秘,但如果你使用过PHP框架(如Symfony),你应该已经看到依赖注入(DI)贯彻这个理念。尽管它们不是完全相互关联的概念,但依赖倒置原则将高级模块的实现细节和创建与低级模块分开。这可以使用依赖注入(DI)来实现。另一个好处是它解耦了模块。耦合让你很难重构,这是一种非常糟糕的开发模式。错误:classEmployee{publicfunctionwork(){//...working}}classRobotextendsEmployee{publicfunctionwork(){//...workingmuchmore}}classManager{private$employee;publicfunction__construct(Employee$employee){$this->employee=$employee;}publicfunctionmanage(){$this->employee->work();}}Good:interfaceEmployee{publicfunctionwork();}classHumanimplementsEmployee{publicfunctionwork(){//....working}}classRobotimplementsEmployee{publicfunctionwork(){//....workingmuchmore}}classManager{private$employee;publicfunction__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();$数据=[$expectedSalary,$experience,$githubLink];render($data);}}functionshowManagerList($managers){foreach($managersas$manager){$expectedSalary=$manager->calculateExpectedSalary();$experience=$manager->getExperience();$githubLink=$manager->getGithubLink();$data=[$expectedSalary,$experience,$githubLink];render($data);}}好:functionshowList($employees){foreach($employees$employee){$expectedSalary=$employee->calculateExpectedSalary();$experience=$employee->getExperience();$githubLink=$employee->getGithubLink();$data=[$expectedSalary,$experience,$githubLink];render($data);}}很好:functionshowList($employees){foreach($employeesas$employee){render([$employee->calculateExpectedSalary(),$employee->getExperience(),$employee->getGithubLink()]);}}后记:虽然OOP设计需要遵守以上原则,但是实际的代码设计一定要简单、简单、简单。在实际编码中,一定要因势利导,盲目跟风,如果不注意实际情况,可能会使你的代码难以理解!
