1. 程式人生 > >面向物件的五大設計原則之單一職責原則

面向物件的五大設計原則之單一職責原則

我們都知道,面向物件是一種高度抽象的思維,我們在面向物件設計中,類是最基本的單位,我們的各種設計都是圍繞著類來進行的,可以這麼說,類與類之間的關係,構成了設計模式的大部分內容,我麼可能認為,類是屬性+函式構成的,事實上在底層儲存上確實也是這麼來搞的,但是這些僅僅只是確定一個獨立的類,而類與類之間的關係,才是設計模式所需要關心的內容,咱們對別的太多的東西也不廢話,這篇文章只是來看下設計模式的五大原則之一的單一職責原則。

我們來通過一個工廠分工產生效率的例子來感受下這個單一職責原則。

假設,一個新手工人去一個汽車製造廠來上班,一輛汽車姑且分為36個部件,如果你讓這個人去幹的話,估計一天他也造不出來一輛車,但是我們如果把這三十六個部件,劃分為三十六條工序,完事每個工序單獨儲存一條流水線,那麼,只需要36的整數倍的工人來製造的話,每個人單獨負責自己所在的流水線,學習自己所在的流水線所需要掌握的知識,一天就可以造出很多輛車,這就是企業管理中的分工,我們在面向物件設計中,叫做單一職責原則(single pesponsibility principle ,簡稱SRP)。

我們可以把職責,看做變化的原因,也就是說,就一個類而言,應該有且僅有一個原因能引起它的變化,這是最簡單,最容易理解,但是最不容易做到的一個設計原則。

簡單來說,就是怎樣設計這個類,以及類的方法的一個界定的問題,這種問題很普遍,比如MVC框架中,很多人會疑惑,我們對於表單插入資料庫欄位過濾和安全檢查應該是放在C層,還是放在M層,這就可以把問題歸到單一職責的範圍裡。

再舉個例子,就是員工類別,我們如果把,工程師,銷售,運營等職業全部放在一個類別中,其結果必然是混亂的,我們需要用一個臃腫且不必要的程式碼段來判斷員工的職業,這其中,不管是哪一種職業發生改變,都需要我們來進行一個改變,這就是搞事情了,我們其實應該明白這個單一職責的原則,如下兩點:

  1. 避免相同的職責,分散到不同的類中
  2. 避免一個類承擔太多的職責

那麼,關於這個單一職責原則的優點也是有的,我們來看下:

1、減少類之間的耦合

如果我們減少類之間的耦合,那麼在需求發生變化的時候,只需要修改一個類,就可以格力這種變化,如果此類含有多種職責,它們耦合在一起, 那麼,但需要變化的時候,就是一個噩夢。

2、提高類的複用性

我們應該瞭解,修電腦比修電視機容易,因為電視機各個部件之間的耦合度太高了,而電腦就不一樣了,記憶體條,硬碟啥的都是可以單獨拆卸和組裝的,所以,那個東西壞了,直接換了就行。

這就是單一職責原則的優勢,我們可以使得各個部件之間,實現方便快捷的拆卸和組裝,而違反了此原則,則會導致該類,在需要修改的時候,分離困難,影響較多的功能模組。

咱們來以資料持久層為例,這個資料持久層就是指資料庫操作,當然,還有快取管理等,我們進行資料操作時,如果是一個複雜的系統,那麼就會涉及到多種資料庫之間的相互讀寫操作,這個時候,就需要我們來設定資料持久層來支援多種資料庫,該怎麼來進行這些操作呢,答案很簡單,使用工廠模式(Factory)。

工廠模式(Factory)允許我們在程式碼執行時例項化物件,之所以被稱為工廠模式,就是因為這個設計模式負責生產物件啊,以咱們剛剛的操作為例子,咱們的這個工廠模式,需要根據我們傳遞的引數的不同,來例項化不同的物件,,就是我們傳入mysql就給我們例項化mysql的物件,使得我們可以根據這個物件來進行相關操作,但是這個工廠類啊,只負責生產物件,而之後的這個物件的具體內容,就不應該歸這個工廠類管了。

來看段程式碼感受下:

//定義一個介面卡
interface db_adapter{
    //連線資料庫
    public function connect($config);

    //執行sql
    public function query($query,$handle);
}

/**
* 操作mysql
*/
class db_mysql implements db_adapter
{

    public function connect($config)
    {
        echo "mysql_connect";
    }

    public function query($query,$handle)
    {
        echo "mysql_query";
    }
}

/**
* 操作sqlite
*/
class db_sqlite implements db_adapter
{
    
    public function connect($config)
    {
        echo "sqlite_connect";
    }

    public function query($query,$handle)
    {
        echo "sqlite_query";
    }
}

/**
* 定義工廠類來處理
*/
class factory_handle
{
    
    public static function factory($classname)
    {
        return new $classname;
    }
}

$db = new factory_handle();
$mysql = $db::factory("db_mysql");
$mysql->connect();
$sqlite = $db::factory("db_sqlite");
$sqlite->query();

上述程式碼呢,首先是定義了一個簡單的介面,完事包含了一些簡單的方法,之後就是db_mysql類實現了介面,再來就是db_sqlite類,也是實現了介面,在這種情況下,如果是要靈活呼叫這兩個類的話,我們就定義了一個簡單的工廠類,這樣一來,我們在程式呼叫的時候,就不需要管是什麼類以及類裡面包含何種方法,我們只需要例項化工廠類,完事按著規範來使用相對應的操作就好了。

工廠類呢,它把具體的物件給解救出來了,使得它們不在必須依靠具體的類,而是抽象,除了這個mysql操作之外,還有就是SNS中的動態實現,也用到了工廠類,但是咱們這次要說的就是單一職責原則,不跑題了啊。

在面向物件的設計模式中,命令模式,也是體現了單一職責原則,這個命令模式會分離命令請求者和命令實現者方面的職責,這麼來看哈,我們要去一個餐館吃飯,餐館有服務員,顧客,廚師三個角色,我們作為顧客,只需要列出選單給服務員,完事由服務員通知廚師來實現這個選單,廚師就只需要在接到這個通知的時候,去做飯,就算是完事類,在這個過程中,命令的請求和實現就完成了解耦,神奇不。。。

咱們來模擬下這個過程哈,程式碼如下:

//廚師類,接受命令
class cooker
{
    public function food()
    {
        echo "food";
    }

    public function drink()
    {
        echo "drink";
    }

    public function end()
    {
        echo "end";
    }
}

//命令介面
interface command
{
    public function execute();
}

//模擬服務員與廚師之間的過程
class food_cooker implements command
{
    private $cooker;

    //繫結命令接受者
    public function __construct(cooker $cooker)
    {
        $this->cooker = $cooker;
    }

    public function execute()
    {
        // TODO: Implement execute() method.
        $this->cooker->food();//讓廚師做飯
    }
}

class drink_cooker implements command
{
    private $cooker;

    //繫結命令接受者
    public function __construct(cooker $cooker)
    {
        $this->cooker = $cooker;
    }

    public function execute()
    {
        // TODO: Implement execute() method.
        $this->cooker->drink();//讓廚師做飯
    }
}

//模擬顧客與服務員之間的過程
class customer
{
    private $food_cooker;
    private $drink_cooker;

    //將命令傳送者繫結到命令接受器上
    public function add_command(command $food_cooker,command $drink_cooker)
    {
        $this->food_cooker = $food_cooker;
        $this->drink_cooker = $drink_cooker;
    }

    public function call_food()
    {
        $this->food_cooker->execute();
    }

    public function call_drink()
    {
        $this->drink_cooker->execute();
    }
}

$obj = new customer();
$cooker = new cooker();
$food_cooker = new food_cooker($cooker);
$drink_cooker = new drink_cooker($cooker);
$obj->add_command($food_cooker,$drink_cooker);
$obj->call_food();
$obj->call_drink();

咱們來簡單梳理下這些程式碼哈,首先咱們要知道,咱們是來模擬個啥的,具體就是我們點菜,完事給服務員,之後讓服務員通知廚師做飯,首先呢,我們先來定義個廚師類(cooker),賦予它做飯的能力,它可以做食物,也可以做喝的,完事呢,我們來定義一個通知的介面(command),給服務員和廚師之間搭建起一個溝通的橋樑,之後就到了服務員出場的時候了,我們平常去餐館,肯定是先叫服務員,不可能直接叫廚師,所以嘞,這個服務員就相當於是我們顧客和廚師之間的一個命令的傳送者,我們接下來就建立做食物的類(food_cooker)和做喝的的類(drink_cooker),有這兩個類,決定服務員可以向廚師傳遞的資訊,最後就是我們顧客類(customer)了,這是我們和服務員溝通的過程,我們可以叫吃的或者是喝的,但是這些都是跟服務員說的。

從上述的例項,我們可以看出,設計模式並非只是一個簡單的理論上的知識,它來源於生活,還有就是不僅僅咱們剛剛說的那兩個設計模式,還有其他的也是包含著單一職責原則,咱就不一一例舉了哈。

我們需要知道的就是,單一職責原則不僅僅對類的設計有意義,還會對我們的模組、子系統為單位的系統架構的設計具有同樣的意義,模組、子系統也應該有且僅有一個引起它變化的原因,像MVC所倡導的各個層之間的相互分離,其實就是單一職責原則在系統總體設計中的具體應用,這個單一職責原則啊,是最簡單也是最難做到的原則之一,因為我們會很自然的將職責連線在一起,而找到並且分離這些職責,就是軟體設計需要達到的目的。

我們來看下在軟體設計中,根據單一職責原則所需要遵循的一些做法。

我們根據業務流程,把業務物件提取出來,如果業務流程的鏈路太過複雜,我們就要把這個業務物件分離為多個單一的業務物件,當我們的業務鏈標準化之後,我們可以對業務物件內部的情況做進一步的處理,把第一次標準化作為最高層抽象,第二次標準化作為次高層抽象,以此類推呢,直到達到恰如其分的設計層次。

我們對於職責的分類,需要注意的是,首先,有業務職責,還需要脫離業務的抽象職責,從認識業務到抽象演算法是一個層層遞進的過程,就好比命令模式中的顧客,服務員以及廚師的職責,我們作為老闆,或者說設計師,只需要規劃好各自的職責範圍就好,既需要防止越俎代庖,也需要防止互相推諉。

好啦,本次記錄就到這裡了。

如果感覺不錯的話,請多多點贊支援哦。。。