PHP代碼簡潔之道——SOLID原則
SOLID 是Michael Feathers推薦的便于記憶的首字母簡寫,它代表了Robert Martin命名的最重要的五個面對對象編碼設計原則:
- S: 單一職責原則 (SRP)
- O: 開閉原則 (OCP)
- L: 里氏替換原則 (LSP)
- I: 接口隔離原則 (ISP)
- D: 依賴反轉(zhuǎn)原則 (DIP)
單一職責原則 Single Responsibility Principle (SRP)
"修改一個類應該只為一個理由"。人們總是易于用一堆方法塞滿一個類,如同我們在飛機上只能攜帶一個行李箱(把所有的東西都塞到箱子里)。這樣做的問題是:從概念上這樣的類不是高內(nèi)聚的,并且留下了很多理由去修改它。將你需要修改類的次數(shù)降低到最小很重要。這是因為,當有很多方法在類中時,修改其中一處,你很難知曉在代碼庫中哪些依賴的模塊會被影響到。
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ù),等)應該對擴展開放,對修改關閉。"這個原則是在說明應該允許用戶在不改變已有代碼的情況下增加新的功能。
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ù)學上講,正方形是一種長方形,但是當你的模型通過繼承使用了"is-a"的關系時,就不對了。
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);
接口隔離原則
接口隔離原則:"客戶端不應該被強制去實現(xiàn)于它不需要的接口"。
有一個清晰的例子來說明示范這條原則。當一個類需要一個大量的設置項,為了方便不會要求客戶端去設置大量的選項,因為在通常他們不需要所有的設置項。使設置項可選有助于我們避免產(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)了它并不需要的方法。所以在這里應該對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)
這條原則說明兩個基本的要點:
- 高階的模塊不應該依賴低階的模塊,它們都應該依賴于抽象
- 抽象不應該依賴于實現(xiàn),實現(xiàn)應該依賴于抽象
這條起初看起來有點晦澀難懂,但是如果你使用過php框架(例如 Symfony),你應該見過依賴注入(DI)對這個概念的實現(xiàn)。雖然它們不是完全相通的概念,依賴倒置原則使高階模塊與低階模塊的實現(xiàn)細節(jié)和創(chuàng)建分離。可以使用依賴注入(DI)這種方式來實現(xiàn)它。更多的好處是它使模塊之間解耦。耦合會導致你難于重構,它是一種非常糟糕的的開發(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();
- }
- }
別寫重復代碼 (DRY)
這條原則大家應該都是比較熟悉了。
盡你***的努力去避免復制代碼,它是一種非常糟糕的行為,復制代碼通常意味著當你需要變更一些邏輯時,你需要修改不止一處。
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設計需要遵守如上原則,不過實際的代碼設計一定要簡單、簡單、簡單。在實際編碼中要根據(jù)情況進行取舍,一味遵守原則,而不注重實際情況的話,可能會讓你的代碼變的難以理解!