你真的懂怎么寫`服務(wù)層`嗎?

其實(shí)很多系統(tǒng)架構(gòu)里面都有服務(wù)層,但是服務(wù)對很多開發(fā)人員來說都有很多不同的定義和寫法。甚至在我待過的公司里都有不同的寫法和編寫模式。每個(gè)人每個(gè)團(tuán)隊(duì)每個(gè)項(xiàng)目都有對服務(wù)不同的理解。那到底什么是服務(wù),怎么理解才是對的呢?

你們有沒有過無數(shù)個(gè)夜晚里嚴(yán)重懷疑人生,琢磨著到底哪一種服務(wù)才是對的?哪一種才是最好的寫法,哪一種才能達(dá)到服務(wù)的真正意義?因?yàn)檫@種執(zhí)著,我開始在國外的各種網(wǎng)站,大神們寫過的開源大項(xiàng)目里面和文章里面總結(jié)出一個(gè)大多數(shù)研發(fā)伙伴們認(rèn)可的理解方式和編寫方式。

要理解什么是服務(wù),我們先來給服務(wù)一個(gè)定義,在系統(tǒng)架構(gòu)里面處于什么角色作用是什么。

服務(wù)定義

角色:服務(wù)是系統(tǒng)架構(gòu)里面的業(yè)務(wù)處理層。
作用:主要是為了高度解耦和封裝不同場景的業(yè)務(wù)和功能到對應(yīng)的服務(wù),然而達(dá)到高度中心化的業(yè)務(wù)代碼。

這個(gè)定義沒毛病吧?贊同的童鞋在評論里舉個(gè)手哈 ??。
好,有了一個(gè)優(yōu)雅高尚的服務(wù)定義,我們來用一個(gè)通俗易懂的例子來理解服務(wù)。

理解服務(wù)

  • 假設(shè)是一個(gè)控制器,現(xiàn)在拿到了一個(gè)衣服對象參數(shù),然后人擁有一個(gè)洗衣服方法
  • 現(xiàn)在人需要洗衣服,但是手洗效率太低了,所以我們寫了一個(gè)多功能的洗衣機(jī)服務(wù)給到人去使用
  • 洗衣機(jī)這個(gè)服務(wù)里面有很多不同洗衣服的方法,但是其實(shí)具體洗衣機(jī)里面的每一個(gè)清洗方法人是不知道怎么實(shí)現(xiàn)的,人都是直接按照提供的功能直接使用。
  • 所以所有服務(wù)里面的方法都是解耦在服務(wù)里面,服務(wù)要提供的方法是可以方便人使用的。

這樣說是不是很好理解了?所以最簡單的理解就是:

服務(wù)是用來封裝業(yè)務(wù)邏輯代碼,是一個(gè)獨(dú)立的邏輯層,高度封裝解耦后提供給控制器或者其他需要用到這個(gè)服務(wù)的地方使用的。

編寫思路

? 錯(cuò)誤例子

把所有洗衣機(jī)的方法提供給人使用,那就等同于讓人來決定所有洗衣機(jī)的參數(shù)和清洗步驟。那人放衣服到洗衣機(jī)后,要選擇先加水,加多少水,然后清洗開始,清洗多久,再甩干等等。

就想想這個(gè)洗衣機(jī)就不想用了,洗個(gè)衣服那么多選項(xiàng),還要想那個(gè)設(shè)置順序才是對的! 我太難了!洗個(gè)雞腿哦!(?`□ ′)?⌒┻━┻

?? 正確例子

洗衣機(jī)服務(wù)實(shí)現(xiàn)了很多不同的常用洗衣服的模式, 比如快速清洗,毛衣清洗,地毯清洗,風(fēng)干,甩干等等。都是一些常用的功能。
每個(gè)功能方法里面其實(shí)調(diào)用了很多洗衣機(jī)封裝好的流程和方法。這樣人使用洗衣機(jī)根本不需要知道這些功能是怎么實(shí)現(xiàn)的,只要知道自己要干嘛,洗衣機(jī)有這個(gè)模式,直接用就好了。

(???)??哇! 介么人性化的么!這種洗衣機(jī)給我來一打謝謝!
思路我們整理清楚了,那么可以開始看看用這種思維模式寫成代碼是怎么樣的。來上機(jī)械鍵盤,開始快樂滴敲代碼了!

服務(wù)寫法

因?yàn)楸救耸怯肞HP做開發(fā)比較多,我這里就用PHP來做服務(wù)的一個(gè)例子,其實(shí)其他語言都是大同小異。只要你懂得服務(wù)的定義。其實(shí)都通用的。

Controller 控制器

首先我們寫一個(gè)人控制器PersonController.php,作為一個(gè)優(yōu)秀的人類,我們天生就會洗衣服,但是人嘛天生就是懶惰的。所以我們買了一臺洗衣機(jī)(實(shí)現(xiàn)洗衣機(jī)服務(wù))并且我們學(xué)會了使用洗衣機(jī)來洗衣服。(實(shí)現(xiàn)wash方法)?(?`?′?)?

一個(gè)人PersonController,有一個(gè)洗衣服方法wash,需要洗衣服的時(shí)候?qū)嵗匆路?wù)new WashingMachineServer(),然后只要把衣服傳入洗衣機(jī)服務(wù)的快洗方法,洗衣機(jī)服務(wù)就會開始快速quickWash($cloth)清洗了。

// 人控制器
class PersonController
{
    /**
    * 洗衣服方法
    * 
    * @param object $cloth 衣服對象
    */
    public function wash($cloth)
    {
        $washingMachine = new WashingMachineService();
        $washingMachine->quickWash($cloth); // 調(diào)用洗衣機(jī)的快速清洗功能
    }
}

我們好奇的童鞋們,肯定會好奇,那這個(gè)洗衣機(jī)(WashingMachineService.php服務(wù)) 到底是怎么實(shí)現(xiàn)的呢?它的快洗功能是怎么做的呢?那我們就來自己建一部洗衣機(jī),自然就懂了。

Service 服務(wù)

動手之前我們要先思考,先分析,養(yǎng)成這樣的好習(xí)慣,代碼再也不難寫了。

分析的重點(diǎn)分為服務(wù)的運(yùn)作流程, 可變動的屬性,最后就是有那些可以提供的模式

  • 洗衣機(jī)應(yīng)該怎么運(yùn)作流程的:
    1. 把衣服放入洗衣機(jī) addCloth()
    2. 注入水到洗衣機(jī)里 addWater()
    3. 開始洗衣服(開始旋轉(zhuǎn)和各種累活)wash()
    4. 把水排除洗衣機(jī) flushWater()
    5. 把衣服取出 fetchClouth()
  • 洗衣機(jī)可變動的屬性
    • 要把衣服放入洗衣機(jī),我們就需要有個(gè)東西來裝著,然后才能清洗,所以我們應(yīng)該有一個(gè)洗衣桶 $bucket
    • 根據(jù)衣服的量,使用的水量是應(yīng)該可以調(diào)節(jié)的。(對我們要節(jié)約用水嘛)$washDuration
  • 洗衣機(jī)最常用的模式
    • 快速洗 quickWash()

?? 需要注意:

  • 所有洗衣機(jī)的內(nèi)部方法都是 private 私有方法,因?yàn)槎际墙o洗衣機(jī)使用的,外部的人是不能使用的;
  • 快速清洗取衣服這兩個(gè)方法是 public 共有方法,因?yàn)槭窍匆聶C(jī)提供出去給人使用的方法;
  • 所有屬性都是 protected 保護(hù)屬性,是洗衣機(jī)獨(dú)有的屬性。

現(xiàn)在我們就要使用程序員的魔法,把以上的邏輯和屬性轉(zhuǎn)換成代碼。(∩?ω?)?----★

class WashingMachineService
{
    /**
    * 清洗時(shí)長 (分鐘)
    * @var integer
    */
    protected $washDuration = 60;
    
    /**
    * 洗衣機(jī)的洗衣桶
    * @var array
    */
    protected $bucket;
    
    /**
    * 改變默認(rèn)洗衣機(jī)的清洗時(shí)長
    * @param integer $duration
    */
    public function changeWashDuration($duration)
    {
        $this->washDuration = intval($duration);
        
        return $this;
    }
    
    /**
    * 往洗衣機(jī)的桶加入水
    */
    private function addWater()
    {
        array_merge($this->bucket, ['water' => 'cold water']);
        
        return $this;
    }
    
    /**
    * 把衣服加入洗衣機(jī)桶內(nèi)
    */
    private function addCloth($cloth)
    {
        array_merge($this->bucket, ['cloths' => $cloth]);
        
        return $this;
    }
    
    /**
    * 旋轉(zhuǎn)桶把開始洗衣服
    */
    private function wash()
    {
        // 使用洗衣機(jī)的清洗時(shí)長來全換清洗衣服
        for ($duration = $this->washDuration; $duration > 0; $duration--) {
            array_rand($this->bucket, 3);
        }
        
        return $this;
    }
    
    /**
    * 把桶里面的水清除掉
    */
    private function flushWater()
    {
        unset($this->bucket['water']);
        
        return $this;
    }
    
    /**
    * 從洗衣桶里面把衣服拿回出來
    */
    private function fetchCloths()
    {
        return $this->bucket['cloths']
    }
    
    /**
    * 快速清洗衣服方法
    */
    public function quickWash($cloth)
    {
        return $this->changeWashDuration(10) // 重新設(shè)置洗衣服的時(shí)長
                    ->addCloth($cloth) // 加入衣服
                    ->addWater() // 加入水
                    ->wash() // 開始清洗
                    ->flushWater() // 清除水
                    ->fetchCloths(); // 最后取出衣服返回
    }
}

以上就是一個(gè)最基礎(chǔ)的服務(wù),有獨(dú)立的內(nèi)部方法可以讓服務(wù)運(yùn)作起來,也有提供出去的服務(wù)模式方法。

?? 需要注意:
服務(wù)的重點(diǎn)特性在最后這個(gè) quickWash 快速清洗方法。實(shí)現(xiàn)快速清洗是通過使用特定順序組合方式調(diào)用洗衣機(jī)內(nèi)部方法。這種服務(wù)的實(shí)現(xiàn)方式,可以把一個(gè)服務(wù)里面的業(yè)務(wù)邏輯拆分成多個(gè)邏輯塊,然后通過不同的順序和組合來實(shí)現(xiàn)某種模式或者功能。這樣的服務(wù)就非常有彈性,而且所有邏輯塊復(fù)用性極高。這個(gè)也是設(shè)計(jì)模式里面的模版方法模式(Template Method)。

上面的例子只是寫了一個(gè)洗衣機(jī)10%不到的功能,一個(gè)完整的洗衣機(jī)還會有很多的邏輯方法。那問題就來了,方法多了這個(gè)服務(wù)就會開始臃腫。這個(gè)時(shí)候我們就要想一套解耦封裝服務(wù)的方式方法。接下來我們來講解一下怎么更深度的服務(wù)封裝。

服務(wù)封裝

在日常開發(fā)過程中,我們有各種各樣的封裝和解耦方式。包括內(nèi)部Trait, 內(nèi)部服務(wù),工廠設(shè)計(jì)模式。這幾種都是可以用來深度封裝服務(wù)的方式方法。找到了方法,下一步就是要找到怎么封裝才是最優(yōu)解耦思路。解耦的原理就是找到共通點(diǎn)公用點(diǎn)。然后把這些方法封裝起來,解耦出去。

封裝思路

在上面寫的洗衣機(jī)服務(wù),里面的洗衣桶是很通用的和獨(dú)立的業(yè)務(wù)邏輯。所以它是可以解耦封裝在一起的。

  • 洗衣機(jī)的bucket洗衣桶屬性的方法其實(shí)可以封裝起來。單獨(dú)做為一個(gè)洗衣桶的服務(wù)。
  • 所有涉及洗衣桶操作的功能和流程都封裝到洗衣桶服務(wù)里面給洗衣機(jī)調(diào)用。

使用上面的邏輯,我們可以把洗衣機(jī)服務(wù)洗衣桶服務(wù)拆分成兩塊。來吧上機(jī)械鍵盤!

封裝編寫

  • 洗衣機(jī)服務(wù) WashingMachineService.php
class WashingMachineService
{
    /**
    * 清洗時(shí)長 (分鐘)
    * @var integer
    */
    protected $washDuration = 60;
    
    /**
    * 改變默認(rèn)洗衣機(jī)的清洗時(shí)長
    * @param integer $duration
    */
    public function changeWashDuration($duration)
    {
        $this->washDuration = intval($duration);
        
        return $this;
    }
    
    /**
    * 快速清洗衣服方法
    */
    public function quickWash($cloth)
    {
        $washingBucket = new WashingBucketService();
        
        $this->changeWashDuration(10) // 重新設(shè)置洗衣服的時(shí)長
        
        // 調(diào)用洗衣機(jī)的桶去清洗衣服
        return $washingBucket->addCloth($cloth) // 加入衣服
                    ->addWater() // 加入水
                    ->wash($this->washDuration) // 開始清洗
                    ->flushWater() // 清除水
                    ->fetchCloths(); // 最后取出衣服返回
    }
}
  • 洗衣桶服務(wù) - WashingBucketService.php
class WashingBucketService
{
    /**
    * 洗衣機(jī)的洗衣桶
    * @var array
    */
    protected $bucket;
    
    /**
    * 往洗衣機(jī)的桶加入水
    */
    public function addWater()
    {
        array_merge($this->bucket, ['water' => 'cold water']);
        
        return $this;
    }
    
    /**
    * 把衣服加入洗衣機(jī)桶內(nèi)
    */
    public function addCloth($cloth)
    {
        array_merge($this->bucket, ['cloths' => $cloth]);
        
        return $this;
    }
    
    /**
    * 旋轉(zhuǎn)桶把開始洗衣服
    */
    public function wash($washDuration)
    {
        // 使用洗衣機(jī)的清洗時(shí)長來全換清洗衣服
        for ($duration = $washDuration; $duration > 0; $duration--) {
            array_rand($this->bucket, 3);
        }
        
        return $this;
    }
    
    /**
    * 把桶里面的水清除掉
    */
    public function flushWater()
    {
        unset($this->bucket['water']);
        
        return $this;
    }
    
    /**
    * 從洗衣桶里面把衣服拿回出來
    */
    public function fetchCloths()
    {
        return $this->bucket['cloths']
    }
}

提供和調(diào)用

模塊與模塊或者系統(tǒng)與系統(tǒng)直接都會使用到服務(wù)來互相打通業(yè)務(wù)。這個(gè)時(shí)候服務(wù)就要有一個(gè)方式提供出去讓外部的模塊或者系統(tǒng)調(diào)用。

?? 需要注意:
這里說的是外部模塊或者系統(tǒng)調(diào)用,這個(gè)是要考慮到如果是微服務(wù)的話,每個(gè)模塊都會在不同的服務(wù)器和域名下,這個(gè)時(shí)候就需要異步調(diào)用。這種情況下如果還是用類實(shí)例的方式來提供和調(diào)用服務(wù)后面要改就很麻煩了。

這種情況下目前最優(yōu)的方式就是服務(wù)提供者用Trait給到服務(wù)使用者來注入到業(yè)務(wù)代碼里面。

  • 洗衣機(jī)服務(wù)Trait - WashingMachineProvider.php
trait WashingMachineProvider
{
    /**
    * 提供洗衣機(jī)服務(wù)類
    */
    public washingMachine()
    {
        return new \WashingMachineService();
    }
}

?? 需要注意:
這里是使用了命名空間來實(shí)例洗衣機(jī)服務(wù)類的。但是如果改成了微服務(wù),那我們只需要改掉所有這些服務(wù)提供Trait,把服務(wù)類實(shí)例改為服務(wù)發(fā)現(xiàn),或者異步服務(wù)調(diào)用就可以了。再也不用花錢去買霸王洗發(fā)水了。?(^?^)?

總結(jié)

經(jīng)歷了千辛萬苦,無數(shù)個(gè)失眠的夜晚。終于知道服務(wù)到底是什么,應(yīng)該怎么寫,怎么寫才是對的。寫好服務(wù)可以提高代碼的維護(hù)性,編寫的代碼也會有更強(qiáng)的邏輯和條理。好的服務(wù)也會有更好的彈性和擴(kuò)張性。下面我們來總結(jié)一下編寫服務(wù)的重點(diǎn)。

角色: 服務(wù)是系統(tǒng)架構(gòu)里面的業(yè)務(wù)處理層。
作用: 主要是為了高度解耦和封裝不同場景的業(yè)務(wù)和功能到對應(yīng)的服務(wù),然而達(dá)到高度中心化的業(yè)務(wù)代碼。
思路: 邏輯要獨(dú)立,分解成邏輯塊,保持復(fù)用性高,盡量不要限定邏輯使用的順序和高彈性的組合性。
編寫: 高度封裝,高內(nèi)聚的原理來編寫服務(wù),細(xì)化分解通用性,公用性的業(yè)務(wù),然后封裝成一個(gè)服務(wù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容