其實(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)作和流程的:- 把衣服放入洗衣機(jī)
addCloth() - 注入水到洗衣機(jī)里
addWater() - 開始洗衣服(開始旋轉(zhuǎn)和各種累活)
wash() - 把水排除洗衣機(jī)
flushWater() - 把衣服取出
fetchClouth()
- 把衣服放入洗衣機(jī)
-
洗衣機(jī)可變動的
屬性:- 要把衣服放入洗衣機(jī),我們就需要有個(gè)東西來裝著,然后才能清洗,所以我們應(yīng)該有一個(gè)洗衣桶
$bucket - 根據(jù)衣服的量,使用的水量是應(yīng)該可以調(diào)節(jié)的。(對我們要節(jié)約用水嘛)
$washDuration
- 要把衣服放入洗衣機(jī),我們就需要有個(gè)東西來裝著,然后才能清洗,所以我們應(yīng)該有一個(gè)洗衣桶
-
洗衣機(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ù)。