面向對象的SOLID例子學習

這些年,月小升同窗發現本身不會讀書因而買了一本《如何閱讀一本書》,發現本身不會作筆記就買了一本《如何作筆記》,寫代碼久了,發現本身一直在用的面向對象不是很瞭解,常常把代碼寫成一坨一坨的,因而回頭來學習怎麼面向對象。那些不熟練的基礎,總要還債的。php

出來混老是要還的java

SOLID 是Michael Feathers推薦的便於記憶的首字母簡寫,它表明了Robert Martin命名的最重要的五個面對對象編碼設計原則數據庫

S: 單一職責原則 (SRP) Single Responsibility Principle
O: 開閉原則 (OCP) Open/Closed Principle
L: 里氏替換原則 (LSP) Liskov Substitution Principle
I: 接口隔離原則 (ISP) Interface Segregation Principle
D: 依賴反轉原則 (DIP) Dependency Inversion Principleide

  1. 單一責任原則:

當須要修改某個類的時候緣由有且只有一個(THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE)。換句話說就是讓一個類只作一種類型責任,當這個類須要承當其餘類型的責任的時候,就須要分解這個類。函數

舉例子:不要讓一個類,負責發郵件,還負責修改客戶名稱
不良代碼:學習

class Email{
public function sendemail(){
//...
}
public function changeUsername(){
//...
}
}
改進版:this

class Email{
public function sendemail(){
//...
}
}
 
class User{
public function changeUsername(){
//...
}
}編碼

  1. 開放封閉原則 Open/Closed Principle (OCP)

軟件實體應該是可擴展,而不可修改的。也就是說,對」擴展是開放的,而對修改是封閉的」。這個原則是在說明應該容許用戶在不改變已有代碼的狀況下增長新的功能。url

實現開閉原則的關鍵就在於「抽象」。把系統的全部可能的行爲抽象成一個抽象底層,這個抽象底層規定出全部的具體實現必須提供的方法的特徵。做爲系統設計的抽象層,要預見全部可能的擴展,從而使得在任何擴展狀況下,系統的抽象底層不需修改;同時,因爲能夠從抽象底層導出一個或多個新的具體實現,能夠改變系統的行爲,所以系統設計對擴展是開放的。設計

看例子

<?php
 
abstract class vehicle
{
protected $name;
public function construct(){
 
}
 
public function getName()
{
return $this->name;
}
}
 
class car extends vehicle
{
public function
construct()
{
parent::construct();
$this->name = 'car';
}
}
 
class bike extends vehicle
{
public function
construct()
{
parent::construct();
 
$this->name = 'bike';
}
}
 
class task
{
private $vehicle;
 
public function
construct($vehicle)
{
$this->vehicle = $vehicle;
}
 
public function dotask($kilometer)
{
$vName = $this->vehicle->getName();
 
if ($vName === 'car') {
return $this->CarRun($kilometer);
} elseif ($vName === 'bike') {
return $this->BikeRun($kilometer);
}
}
 
private function CarRun($kilometer)
{
echo '小汽車跑了'.$kilometer.'千米<hr>';
}
 
private function BikeRun($kilometer)
{
echo '自行車跑了'.$kilometer.'千米<hr>';
}
}
 
echo '<meta charset="utf-8">';
$car = new car;
$bike = new bike;
$task = new task($car);
$task->dotask(10);
$task = new task($bike);
$task->dotask(10);
?>
這個例子,若是增長一個車,bus,那麼就要改動task任務這個類的底層代碼

改良版本

<?php
 
interface vehicle
{
public function run($url);
}
 
class car implements vehicle
{
public function run($kilometer)
{
echo '小汽車跑了'.$kilometer.'千米<hr>';
}
}
 
class bike implements vehicle
{
public function run($kilometer)
{
echo '自行車跑了'.$kilometer.'千米<hr>';
}
}
 
class task
{
private $vehicle;
 
public function __construct($vehicle)
{
$this->vehicle = $vehicle;
}
 
public function dotask($kilometer)
{
$this->vehicle->run($kilometer);
}
 
}
 
echo '<meta charset="utf-8">';
$car = new car;
$bike = new bike;
$task = new task($car);
$task->dotask(10);
$task = new task($bike);
$task->dotask(10);
?>
改良後的車輛,任意增長新車型,都不會改動task的內容

  1. 里氏替換原則

當一個子類的實例應該可以替換任何其超類的實例時,它們之間才具備is-A關係
能夠理解爲:只要有父類出現的地方,均可以使用子類來替代。並且不會出現任何錯誤或者異常。可是反過來卻不行。子類出現的地方,不能使用父類來替代。

定義1:若是對每個類型爲 T1的對象 o1,都有類型爲 T2 的對象o2,使得以 T1定義的全部程序 P 在全部的對象 o1 都代換成 o2 時,程序 P 的行爲沒有發生變化,那麼類型 T2 是類型 T1 的子類型。
理解爲「只要有父類出現的地方,均可以使用子類來替代」

定義2:全部引用基類的地方必須能透明地使用其子類的對象。
問題由來:有一功能P1,由類A完成。現須要將功能P1進行擴展,擴展後的功能爲P,其中P由原有功能P1與新功能P2組成。新功能P由類A的子類B來完成,則子類B在完成新功能P2的同時,有可能會致使原有功能P1發生故障。

解決方案:當使用繼承時,遵循里氏替換原則。類B繼承類A時,除添加新的方法完成新增功能P2外,儘可能不要重寫父類A的方法,也儘可能不要重載父類A的方法。

不良的設計:違背了里氏替換原則
假設一個父親廚師會個炒雞蛋的手藝,辣椒炒雞蛋,兒子應該會這個手藝,可是兒子不會用辣椒,只會用大蔥,因此炒出來的雞蛋不同了。

<?php
class father{
public function cookeEgg(){
echo '辣椒炒雞蛋<br/>';
}
}
 
class son1 extends father{
public function cookeEgg(){
echo '大蔥炒雞蛋<br/>';
}
 
}
echo '<meta charset="utf-8">';
$f = new father;
$f->cookeEgg();
$s1 = new son1;
$s1->cookeEgg();
?>
辣椒炒雞蛋
大蔥炒雞蛋
違背了原則1:只要有父類出現的地方,均可以使用子類來替代。
如今父親會辣椒炒雞蛋,換成兒子來炒,結果兒子由於不敢碰辣椒,炒成了大蔥燒雞蛋。 這個兒子就不是好兒子。咱們假設這個是大兒子。

改良版本:出來一個好的二兒子的樣子

<?php
class father{
public function cookeEgg(){
echo '辣椒炒雞蛋<br/>';
}
}
class son2 extends father{
public function cookeEggWithScallion(){
echo '大蔥炒雞蛋<br/>';
}
}
echo '<meta charset="utf-8">';
$f = new father;
$f->cookeEgg();
$s2 = new son2;
$s2->cookeEgg();
$s2->cookeEggWithScallion();
?>
辣椒炒雞蛋
辣椒炒雞蛋
大蔥炒雞蛋
這個兒子,複合了定義1,父親出來的地方,兒子出來就能提到,父親會辣椒炒雞蛋,兒子也會,因此兒子自動繼承,可是二兒子還會大蔥炒雞蛋,二兒子就會兩個炒蛋了

按解決方案:不要重寫父親的方法
規則1: 不要重寫父親的方案
規則2: 全部孩子都會有父親的技能(父親能辣椒炒雞蛋,兒子就會,父親出現的地方,兒子就能夠替代)
規則3: 孩子會額外的技能,本身單獨再寫函數。(子類出現的地方,父親不必定能替代)

  1. 依賴反轉原則

  2. 高層模塊不該該依賴於低層模塊,兩者都應該依賴於抽象
  3. 抽象不該該依賴於細節,細節應該依賴於抽象

實際理解爲:高級邏輯層代碼,不要由於底層的模塊代碼改變而變更。

高層模塊:業務邏輯層,好比羣發郵件,我決定發給購買者和沒有購買者,那麼羣發郵件這個send的工做就是業務邏輯層的高層模塊,而決定哪些用戶時購買者的底層數據庫查詢操做屬於底層模塊。

舉例子:老張開寶馬,這個動做,開車是業務邏輯層,寶馬車跑動是底層。
不良設計

<?php
class Bwm{
public function run(){
echo "開動寶馬汽車";
}
}
 
class Audi{
public function run(){
echo "開動奧迪汽車";
}
}
 
//高層模塊Driver 依賴了底層模塊Bwm , 出來模塊C Audi,我就只好改Driver了。
class Driver{
public function drive(Bwm $car){
$car->run();
}
}
 
echo '<meta charset="utf-8">';
$bwm = new Bwm;
$zhang = new Driver();
$zhang->drive($bwm);
 
$audi = new Audi;
$zhang->drive($audi); //奧迪我無法開了。 此處代碼會報錯。
?>
有個辦法就是業務層,再寫一個函數funciton driveAudi() 是否是很熟悉,咱們由於要負責處理額外的狀況,又寫了個看起來很重複函數。

改良的版本,把車作成接口
寶馬和奧迪都是來實現車的底層邏輯函數。這樣再新車進入的時候,就不用改動邏輯層的代碼了。

<?php
interface Car{
public function run();
}
class Bwm implements Car{
public function run(){
echo "開動寶馬汽車";
}
}
class Audi implements Car{
public function run(){
echo "開動奧迪汽車";
}
}
class Driver{
public function drive(Car $car){
$car->run();
}
}
 
echo '<meta charset="utf-8">';
$bwm = new Bwm;
$zhang = new Driver();
$zhang->drive($bwm);
 
$audi = new Audi;
$zhang->drive($audi);
?>
如今老張能夠開寶馬也能夠開奧迪,你拿個大衆,我也照樣開。

再次理解這句話:高級邏輯層代碼,不要由於底層的模塊代碼改變而變更。

  1. 接口分離原則

不能強迫用戶去依賴那些他們不使用的接口。換句話說,使用多個專門的接口比使用單一的總接口總要好。

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
}
}
改良版本

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
}
}
對面向對象的領悟,有助於在大型代碼量的工程裏,實現有效分離函數,互不干擾,團隊協做。

https://java-er.com/blog/solid-class-study/

相關文章
相關標籤/搜索