面向對象編程的六大原則
阿新 • • 發佈:2017-10-29
關閉 做出 函數 系統管理員 就會 實體 其他 brush 裏氏替換
一.單一職責:
不要存在多於一個導致類變更的原因。通俗的說,即一個類只負責一項職責。
我們最開始設計了一個類Human,我們賦予了四項功能.
以下是偽代碼:
class Human { public function 教書(); public function 講課(); public function 聽課(); public function 寫作業(); } 但我們發現,一個人不會既教書,又寫作業,也不會既聽課,又講課. 所以我們將其拆分: class Teacher { public function 教書(); public function 講課(); } class Student { public function 聽課(); public function 寫作業(); } 這就比較好的符合單一職責.
二.裏氏替換原則:
所有引用基類的地方必須能透明地使用其子類的對象,也就是說子類可以擴展父類的功能,但不能改變父類原有的功能
偽代碼: class 基類 { public function say(){return ‘基類‘;} } class 子類 extends 基類 { public function say($msg) { return $msg; } } $a = new 基類(); $b = new 子類(); echo $b->say(‘子類‘); 子類重寫了父類的方法,say要傳入參數,導致了導致了父類原有的功能被改變了,這違背了裏氏替換原則.
三.依賴倒置:
高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象。簡單的說就是盡量面向接口編程.
我們需要在用戶積分增加的時候,寫入一個日誌文件.最開始我們寫入文件中. class User { ................ public function addPoint($dValue, Log log) { $this->point += $dValue; $log->writeLog($this); } ............... } class Log { public function writeLog(User $user) { write_log_to_file.... } } // 調用 $user = new User(); $user->addPoint(100, new Log()); 後面需求有變化,需要寫入數據庫 class User { ................ public function addPoint($dValue, Log log) { $this->point += $dValue; $log->writeLog($this); } ............... } class DbLog { public function writeLog(User $user) { write_log_to_db.... } } // 調用 $user = new User(); $user->addPoint(100, new Log()); 代碼改動頗大,每次需求的變化都需要改動許多代碼,我們現在建立一個接口,所有的日誌類都繼承該接口.我們讓User類中的addPoint依賴該接口. interface ILog{ public function writeLog(User $user); } class Log implements ILog { public function writeLog(User $user) { write_log_to_file.... } } class DbLog implements ILog { public function writeLog(User $user) { write_log_to_db.... } } class User { ................ public function addPoint($dValue, ILog log) { $this->point += $dValue; $log->writeLog($this); } ............... } // 調用 $user = new User(); $user->addPoint(100, new Log()); $user->addPoint(100, new DbLog()); 這樣子後面無論再增加其他日誌類,只要實現了ILog,均可以調用.
四.接口隔離:
客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。接口最小化,過於臃腫的接口依據功能,可以將其拆分為多個接口.
interface IUser{ 註冊接口 登錄接口 充值接口 充值記錄接口 手動充值(用於充值失敗時,手動補發) } 但實際中,我們發現手動充值在User類中用不到,因為這顯然是個後臺管理用戶才可以用到.不符合接口隔離原則.我們拆分該接口. interface IUser { 註冊接口 登錄接口 充值接口 充值記錄接口 } interface IManageUser { 手動充值(用於充值失敗時,手動補發) }
五.迪米特法則:
一個對象應該對其他對象保持最少的了解,簡單的理解就是高內聚,低耦合,一個類盡量減少對其他對象的依賴,並且這個類的方法和屬性能用私有的就盡量私有化.
class Product { ...... public function buy(){...} public function repertory(){...} ...... } 這是一個產品類,我們期望用戶在購買掉一個產品後,該產品的庫存減去1.用戶購買了,產品庫存才會減去1.減庫存,依賴於購買.所以我們應該將repertory聲明為private,其他對象也只需要了解該對象的buy即可. class User { ........ public function log(Log $log) { $log->writeLog(.....); } ........ } class Log { // 寫日誌 public function writeLog(User $user) { $log->writeLog(.....); } // 打印日誌 public function printLog(User $user) { ..... } } 我們期望對User類的某些操作,記錄日誌.但User類log方法依賴Log類,但Log類的printLog方法,User類並不需要,這應該要看日誌的系統管理員如:AdminUser需要的方法. 去掉Log類的printLog方法 class Log { // 寫日誌 public function writeLog(User $user) { $log->writeLog(.....); } } 或者面向接口: interface ILog { public function writeLog(User $user); } class Log implements ILog { // 寫日誌 public function writeLog(User $user) { $log->writeLog(.....); } // 打印日誌 public function printLog(User $user) { ..... } } class User { ........ public function log(ILog $log) { $log->writeLog(.....); } ........ }
六.開閉原則:
一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉.當軟件需求變化時,盡量通過擴展軟件實體的行為來實現變化,而不是通過修改已有的代碼來實現變化.
我們有個產品基類,提供了一些列的方法: class Product { ............ 一系列的方法 ............ } 但隨著業務的發展,我們需要Product能夠提供打折功能.這個時候我們是直接在Product中增加一個discount方法?還是再擴展一個子類? 打折顯然不是所有產品都需要的,如果所有產品都需要,我們一開始就會將其設計進去.可能只是BookProduct需要,所以我們應該擴展一個子類.這既沒有修改原有的類,又擴展了該類的功能.符合了開閉原則. class DiscountProduct extends Product { ............ discount() ............ }
ps:過於嚴苛的遵守六大原則,將分層變多,類變多,項目變的非常龐大.所以對這些原則要根據實際情況做出取舍.一般分層不要超過6層.超過6層,代碼變的難以維護也難以跟蹤.
面向對象編程的六大原則