1. 程式人生 > >面向對象編程的六大原則

面向對象編程的六大原則

關閉 做出 函數 系統管理員 就會 實體 其他 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層,代碼變的難以維護也難以跟蹤.

面向對象編程的六大原則