PHP代碼簡潔之道——SOLID原則
SOLID 是Michael Feathers推薦的便于記憶的首字母簡寫,它代表了Robert Martin命名的最重要的五個面對對象編碼設(shè)計原則:
- S: 單一職責(zé)原則 (SRP)
 - O: 開閉原則 (OCP)
 - L: 里氏替換原則 (LSP)
 - I: 接口隔離原則 (ISP)
 - D: 依賴反轉(zhuǎn)原則 (DIP)
 
單一職責(zé)原則 Single Responsibility Principle (SRP)
"修改一個類應(yīng)該只為一個理由"。人們總是易于用一堆方法塞滿一個類,如同我們在飛機上只能攜帶一個行李箱(把所有的東西都塞到箱子里)。這樣做的問題是:從概念上這樣的類不是高內(nèi)聚的,并且留下了很多理由去修改它。將你需要修改類的次數(shù)降低到最小很重要。這是因為,當(dāng)有很多方法在類中時,修改其中一處,你很難知曉在代碼庫中哪些依賴的模塊會被影響到。
Bad:
- class UserSettings{
 - private $user;
 - public function __construct($user)
 - {
 - $this->user = $user;
 - }
 - public function changeSettings($settings)
 - {
 - if ($this->verifyCredentials()) {
 - // ...
 - }
 - }
 - private function verifyCredentials()
 - {
 - // ...
 - }
 - }
 
Good:
- class UserAuth {
 - private $user;
 - public function __construct($user){
 - $this->user = $user;
 - }
 - public function verifyCredentials(){
 - // ...
 - }
 - }
 - class UserSettings {
 - private $user;
 - private $auth;
 - public function __construct($user) {
 - $this->user = $user;
 - $this->auth = new UserAuth($user);
 - }
 - public function changeSettings($settings){
 - if ($this->auth->verifyCredentials()) {
 - // ...
 - }
 - }
 - }
 
開閉原則 Open/Closed Principle (OCP)
正如Bertrand Meyer所述,"軟件的實體(類, 模塊, 函數(shù),等)應(yīng)該對擴展開放,對修改關(guān)閉。"這個原則是在說明應(yīng)該允許用戶在不改變已有代碼的情況下增加新的功能。
Bad:
- abstract class Adapter{
 - protected $name;
 - public function getName(){
 - return $this->name;
 - }
 - }
 - class AjaxAdapter extends Adapter{
 - public function __construct(){
 - parent::__construct();
 - $this->name = 'ajaxAdapter';
 - }
 - }
 - class NodeAdapter extends Adapter{
 - public function __construct(){
 - parent::__construct();
 - $this->name = 'nodeAdapter';
 - }
 - }
 - class HttpRequester{
 - private $adapter;
 - public function __construct($adapter)
 - {
 - $this->adapter = $adapter;
 - }
 - public function fetch($url)
 - {
 - $adapterName = $this->adapter->getName();
 - if ($adapterName === 'ajaxAdapter') {
 - return $this->makeAjaxCall($url);
 - }
 - elseif ($adapterName === 'httpNodeAdapter') {
 - return $this->makeHttpCall($url);
 - }
 - }
 - private function makeAjaxCall($url)
 - { // request and return promise
 - }
 - private function makeHttpCall($url)
 - { // request and return promise
 - }
 - }
 
在上面的代碼中,對于HttpRequester類中的fetch方法,如果我新增了一個新的xxxAdapter類并且要在fetch方法中用到的話,就需要在HttpRequester類中去修改類(如加上一個elseif 判斷),而通過下面的代碼,就可很好的解決這個問題。下面代碼很好的說明了如何在不改變原有代碼的情況下增加新功能。
Good:
- interface Adapter{
 - public function request($url);
 - }
 - class AjaxAdapter implements Adapter{
 - public function request($url)
 - { // request and return promise
 - }
 - }
 - class NodeAdapter implements Adapter{
 - public function request($url)
 - { // request and return promise
 - }
 - }
 - class HttpRequester{
 - private $adapter;
 - public function __construct(Adapter $adapter)
 - { $this->adapter = $adapter;
 - }
 - public function fetch($url)
 - { return $this->adapter->request($url);
 - }
 - }
 
里氏替換原則 Liskov Substitution Principle (LSP)
對這個概念***的解釋是:如果你有一個父類和一個子類,在不改變原有結(jié)果正確性的前提下父類和子類可以互換。這個聽起來讓人有些迷惑,所以讓我們來看一個經(jīng)典的正方形-長方形的例子。從數(shù)學(xué)上講,正方形是一種長方形,但是當(dāng)你的模型通過繼承使用了"is-a"的關(guān)系時,就不對了。
Bad:
- class Rectangle{
 - protected $width = 0;
 - protected $height = 0;
 - public function render($area)
 - { // ...
 - }
 - public function setWidth($width)
 - { $this->width = $width;
 - }
 - public function setHeight($height)
 - { $this->height = $height;
 - }
 - public function getArea()
 - { return $this->width * $this->height;
 - }
 - }
 - class Square extends Rectangle{
 - public function setWidth($width)
 - {
 - $this->width = $this->height = $width;
 - }
 - public function setHeight(height)
 - { $this->width = $this->height = $height;
 - }
 - }
 - function renderLargeRectangles($rectangles){
 - foreach ($rectangles as $rectangle) {
 - $rectangle->setWidth(4);
 - $rectangle->setHeight(5);
 - $area = $rectangle->getArea(); // BAD: Will return 25 for Square. Should be 20.
 - $rectangle->render($area);
 - }
 - }
 - $rectangles =
 - [new Rectangle(), new Rectangle(), new Square()];
 - renderLargeRectangles($rectangles);
 
Good:
- abstract class Shape{
 - protected $width = 0;
 - protected $height = 0;
 - abstract public function getArea();
 - public function render($area) { // ...
 - }
 - }
 - class Rectangle extends Shape{
 - public function setWidth($width)
 - { $this->width = $width;
 - }
 - public function setHeight($height)
 - { $this->height = $height;
 - }
 - public function getArea()
 - { return $this->width * $this->height;
 - }
 - }
 - class Square extends Shape{
 - private $length = 0;
 - public function setLength($length)
 - { $this->length = $length;
 - }
 - public function getArea()
 - { return pow($this->length, 2);
 - }
 - }
 - function renderLargeRectangles($rectangles){
 - foreach ($rectangles as $rectangle) {
 - if ($rectangle instanceof Square) {
 - $rectangle->setLength(5);
 - } elseif ($rectangle instanceof Rectangle) {
 - $rectangle->setWidth(4);
 - $rectangle->setHeight(5);
 - }
 - $area = $rectangle->getArea();
 - $rectangle->render($area);
 - }
 - }
 - $shapes = [new Rectangle(), new Rectangle(), new Square()];
 - renderLargeRectangles($shapes);
 
接口隔離原則
接口隔離原則:"客戶端不應(yīng)該被強制去實現(xiàn)于它不需要的接口"。
有一個清晰的例子來說明示范這條原則。當(dāng)一個類需要一個大量的設(shè)置項,為了方便不會要求客戶端去設(shè)置大量的選項,因為在通常他們不需要所有的設(shè)置項。使設(shè)置項可選有助于我們避免產(chǎn)生"胖接口"
Bad:
- interface Employee{
 - public function work();
 - public function eat();
 - }
 - class Human implements Employee{
 - public function work()
 - { // ....working
 - }
 - public function eat()
 - { // ...... eating in lunch break
 - }
 - }class Robot implements Employee{
 - public function work()
 - { //.... working much more
 - }
 - public function eat()
 - { //.... robot can't eat, but it must implement this method
 - }
 - }
 
上面的代碼中,Robot類并不需要eat()這個方法,但是實現(xiàn)了Emplyee接口,于是只能實現(xiàn)所有的方法了,這使得Robot實現(xiàn)了它并不需要的方法。所以在這里應(yīng)該對Emplyee接口進行拆分,正確的代碼如下:
Good:
- interface Workable{
 - public function work();
 - }
 - interface Feedable{
 - public function eat();
 - }
 - interface Employee extends Feedable, Workable{
 - }
 - class Human implements Employee{
 - public function work()
 - { // ....working
 - }
 - public function eat()
 - { //.... eating in lunch break
 - }
 - }// robot can only work
 - class Robot implements Workable{
 - public function work()
 - { // ....working
 - }
 - }
 
依賴反轉(zhuǎn)原則 Dependency Inversion Principle (DIP)
這條原則說明兩個基本的要點:
- 高階的模塊不應(yīng)該依賴低階的模塊,它們都應(yīng)該依賴于抽象
 - 抽象不應(yīng)該依賴于實現(xiàn),實現(xiàn)應(yīng)該依賴于抽象
 
這條起初看起來有點晦澀難懂,但是如果你使用過php框架(例如 Symfony),你應(yīng)該見過依賴注入(DI)對這個概念的實現(xiàn)。雖然它們不是完全相通的概念,依賴倒置原則使高階模塊與低階模塊的實現(xiàn)細節(jié)和創(chuàng)建分離。可以使用依賴注入(DI)這種方式來實現(xiàn)它。更多的好處是它使模塊之間解耦。耦合會導(dǎo)致你難于重構(gòu),它是一種非常糟糕的的開發(fā)模式。
Bad:
- class Employee{
 - public function work()
 - { // ....working
 - }
 - }
 - class Robot extends Employee{
 - public function work() { //.... working much more
 - }
 - }
 - class Manager{
 - private $employee;
 - public function __construct(Employee $employee)
 - { $this->employee = $employee;
 - } public function manage()
 - { $this->employee->work();
 - }
 - }
 
Good:
- interface Employee{
 - public function work();
 - }
 - class Human implements Employee{
 - public function work()
 - { // ....working
 - }
 - }
 - class Robot implements Employee{
 - public function work()
 - { //.... working much more
 - }
 - }
 - class Manager{
 - private $employee;
 - public function __construct(Employee $employee)
 - { $this->employee = $employee;
 - } public function manage()
 - { $this->employee->work();
 - }
 - }
 
別寫重復(fù)代碼 (DRY)
這條原則大家應(yīng)該都是比較熟悉了。
盡你***的努力去避免復(fù)制代碼,它是一種非常糟糕的行為,復(fù)制代碼通常意味著當(dāng)你需要變更一些邏輯時,你需要修改不止一處。
Bad:
- function showDeveloperList($developers){
 - foreach ($developers as $developer) {
 - $expectedSalary =
 - $developer->calculateExpectedSalary();
 - $experience = $developer->getExperience();
 - $githubLink = $developer->getGithubLink();
 - $data = [
 - $expectedSalary,
 - $experience,
 - $githubLink
 - ];
 - render($data);
 - }
 - }
 - function showManagerList($managers){
 - foreach ($managers as $manager) {
 - $expectedSalary =
 - $manager->calculateExpectedSalary();
 - $experience = $manager->getExperience();
 - $githubLink = $manager->getGithubLink();
 - $data = [
 - $expectedSalary,
 - $experience,
 - $githubLink
 - ];
 - render($data);
 - }
 - }
 
Good:
- function showList($employees){
 - foreach ($employees as $employee) {
 - $expectedSalary =
 - $employee->calculateExpectedSalary();
 - $experience = $employee->getExperience();
 - $githubLink = $employee->getGithubLink();
 - $data = [
 - $expectedSalary,
 - $experience,
 - $githubLink
 - ];
 - render($data);
 - }
 - }
 
Very good:
- function showList($employees){ foreach ($employees as $employee) {
 - render([
 - $employee->calculateExpectedSalary(),
 - $employee->getExperience(),
 - $employee->getGithubLink()
 - ]);
 - }
 - }
 
后記:雖然OOP設(shè)計需要遵守如上原則,不過實際的代碼設(shè)計一定要簡單、簡單、簡單。在實際編碼中要根據(jù)情況進行取舍,一味遵守原則,而不注重實際情況的話,可能會讓你的代碼變的難以理解!















 
 
 











 
 
 
 