為什么您的代碼需要抽象層?
譯文【51CTO.com快譯】抽象是編寫設(shè)計良好的軟件最重要的方面之一。
了解這個基本概念將為您提供可遵循的系統(tǒng)和清晰的思維模型,以了解如何創(chuàng)建好的抽象。
好的抽象降低了復(fù)雜性,并允許開發(fā)人員更輕松地更改代碼并減少錯誤。但是創(chuàng)建抽象并非易事。那么您究竟如何做到這一點,需要采取哪些步驟?
什么是抽象?
談?wù)摯a中的抽象層之前,不妨簡要地談?wù)劤橄笫鞘裁础?/p>
抽象可以定義為通過以下方式簡化實體的過程:
1. 省略不重要的細(xì)節(jié)。
2. 暴露接口。
所有抽象在這方面都大同小異。
自動駕駛汽車是抽象的實際例子。在這種情況下,離合器是抽象的,駕駛員可以更輕松地?fù)Q檔。
抽象也有不足。比如說,雖然駕駛員可以更輕松地?fù)Q檔,但現(xiàn)在對汽車的控制也較少,因此為賽車駕駛員抽象離合器可能是壞主意。
作者John Ousterhout在《軟件設(shè)計理念》一書中談到了抽象可能出錯的兩種方式:
1. 包含不重要的細(xì)節(jié):由于包含不重要的細(xì)節(jié),抽象變得過于復(fù)雜,導(dǎo)致開發(fā)人員的認(rèn)知負(fù)擔(dān)加大。
2. 省略重要細(xì)節(jié):Ousterhout將這種抽象稱為“虛假抽象”,因為查看抽象的開發(fā)人員不會擁有他們需要的所有信息。
所以,好的抽象需要兼顧和權(quán)衡。
代碼中的抽象
我們已知道了抽象,但它如何應(yīng)用于代碼?
所有代碼可以歸類為策略或細(xì)節(jié)。
- 策略:這些是實體和業(yè)務(wù)邏輯。
- 細(xì)節(jié):這是策略的實現(xiàn)。細(xì)節(jié)執(zhí)行策略。
假設(shè)您有一個 User 實體。用戶有某個接口以及某個業(yè)務(wù)邏輯。這個User實體還有組,您被指派編寫獲取所有用戶組的代碼。
在這里,策略是用戶本身,因為它是一個實體,但它也是getUserGroups函數(shù),因為它是與該實體相關(guān)的業(yè)務(wù)邏輯。
它如何實現(xiàn)、使用哪個數(shù)據(jù)庫、使用哪個ORM(對象關(guān)系映射)、使用哪些庫、如何編寫代碼以及所有不同的實現(xiàn),這些都是代碼的細(xì)節(jié)部分。
在您的代碼中,您希望在隱藏細(xì)節(jié)的同時暴露策略。策略和細(xì)節(jié)之間的這種分離讓您可以切換和輕松重構(gòu)實現(xiàn)。
如果您的策略和細(xì)節(jié)是耦合的,就很難重構(gòu),因為它們混合在一起,更改會從一個傳播到另一個。
在設(shè)計良好的系統(tǒng)中,策略和細(xì)節(jié)之間的分離是關(guān)鍵。
那么這如何應(yīng)用于抽象層呢?
抽象層
抽象層暴露了接口,并隱藏了它背后的實現(xiàn)細(xì)節(jié)。
抽象層的目的是創(chuàng)建抽象。層里面的方法和屬性應(yīng)該是暴露的接口,而這些方法里面的實現(xiàn)是細(xì)節(jié)層中的一切。
創(chuàng)建抽象層主要有三個好處:
1. 集中:通過在一層中創(chuàng)建抽象,與其相關(guān)的所有內(nèi)容都是集中的,因此可以在一處進(jìn)行任何更改。集中與“不要重復(fù)自己”(DRY)原則有關(guān),這很容易被誤解。
DRY不僅涉及代碼的重復(fù),還涉及知識的重復(fù)。有時,兩個不同的實體可以復(fù)制相同的代碼,因為這可以實現(xiàn)分離,允許這些實體將來分別演進(jìn)。
2. 簡化:通過創(chuàng)建抽象層,您可以暴露特定的功能并隱藏實現(xiàn)細(xì)節(jié)?,F(xiàn)在代碼可以直接與您的接口交互,避免處理不相關(guān)的實現(xiàn)細(xì)節(jié)。這提高了代碼的可讀性,減輕了閱讀代碼的開發(fā)人員的認(rèn)知負(fù)擔(dān)。為何?
因為策略不如細(xì)節(jié)復(fù)雜,所以與其交互更直接。
3. 測試:抽象層非常適合測試,因為您可以把細(xì)節(jié)換成另一組細(xì)節(jié),這有助于隔離正在測試的區(qū)域,并正確創(chuàng)建測試替代(test doubles)。
測試代碼時,開發(fā)人員需要測試特定的功能,同時為某些功能創(chuàng)建測試替代,以避免調(diào)用真正的數(shù)據(jù)庫之類的對象。策略和細(xì)節(jié)糾纏在一起時,過度使用測試替代很常見,這使得覆蓋率更低,測試的用處也大大降低。
為數(shù)據(jù)庫實現(xiàn)對象創(chuàng)建抽象層時,開發(fā)人員可以替換該層,確保在測試其余功能時僅替換數(shù)據(jù)庫響應(yīng)。
創(chuàng)建抽象層的示例
假設(shè)您為組創(chuàng)建API編寫代碼:
- function createUserGroup(group, userId) {
- logger.info('Creating group for user ${userId}')
- db.startTransaction();
- const isValidGroup = validateGroup(group);
- if (!isValidGroup) throw new Error('Invalid group');
- db.addDoc('groups', group)
- dc.addDoc('quotas/groups', 1)
- .
- .
- .
- }
可從上述例子看出,該函數(shù)邏輯與策略和細(xì)節(jié)混合在一起。它處理很多不同的功能,并不使用任何抽象層。
這是使用抽象層的代碼:
- class GroupsService {
- GROUPS_COLLECTION = 'groups';
- createGroup() {
- db.startTransaction();
- const isValid = this.validateGroup();
- if (!isValid) throw new Error('Invalid group')
- db.addDoc(GROUPS_COLLECTION, group)
- quotasService.setQuota('/groups', 1);
- db.finishTransaction();
- }
- validateGroup()
- deleteGroup();
- }
- class QuotasService {
- setQuota(collection: string, value: any) {
- dc.addDoc(`quotas/${collection}`, value)
- }
- }
- function createUserGroup(group, userId) {
- logger.info(`Creating group for user ${userId}`)
- groupsService.createGroup();
- return {
- status: 200,
- message: 'Group created successfully'
- }
- }
第二個實現(xiàn)有諸多好處:
1. 更容易理解,因為實現(xiàn)細(xì)節(jié)是抽象的,您在閱讀的是與策略交互的代碼。
2. 一切都集中在一項服務(wù)中。想象一下與組有關(guān)的代碼散布在整個應(yīng)用程序中。所做的每一次更改都需要到處進(jìn)行;至少可以說,這會有問題。
3. 代碼更加封裝。注意控制器createUserGroup現(xiàn)在不知道配額,只知道組創(chuàng)建,因為配額無關(guān)緊要。
4. 我們可以專注于測試實現(xiàn),同時僅把細(xì)節(jié)層換成測試替代,使測試更容易。至于集成測試,我們可以替換QuotaService和GroupService,并測試該特定控制器所實現(xiàn)的實現(xiàn)。
可能的應(yīng)用
抽象層可以通過許多不同的方式實現(xiàn),其中最常見的用例是:
1. 通過分離策略和細(xì)節(jié)創(chuàng)建更精簡的組件:如果變更和重構(gòu)很容易,您的代碼將通過時間的考驗。分離策略和細(xì)節(jié),同時僅用接口保持組件之間的交互提供了未來代碼演變所需的基礎(chǔ)設(shè)施。
2. 包裝第三方庫:您的代碼中過時的第三方庫阻止您升級其他依賴項是一場噩夢,如果該依賴項存在安全風(fēng)險,尤為糟糕。
通過在一個中央抽象層中使用您自己的接口包裝第三方庫,變得將很容易,因為它們只需要在暴露接口的那一處進(jìn)行。
3. 創(chuàng)建實用服務(wù):實用服務(wù)是提高開發(fā)速度和重用通用代碼段的關(guān)鍵方法。
比如說,如果您在開發(fā)處理大量不同時間和日期功能的特性,為什么不創(chuàng)建幾個實用函數(shù)來幫助您、并將它們放在一處供進(jìn)一步重用?
小結(jié)
創(chuàng)建抽象層通過提供三大好處來幫助顯著改進(jìn)代碼:集中、簡化和更好的測試。
請記住,抽象層和一般的抽象不是目的,而是實現(xiàn)目的的手段。抽象可能有缺點。一個常見的例子是某些抽象會影響性能。所以總是要先了解不足。
原文標(biāo)題:Why Your Code Needs Abstraction Layers,作者:Yair Cohen
【51CTO譯稿,合作站點轉(zhuǎn)載請注明原文譯者和出處為51CTO.com】



























