2023-01-02 分類: 網(wǎng)站建設(shè)
API設(shè)計新思維:用流暢接口構(gòu)造內(nèi)部DSL
程序設(shè)計語言的抽象機制包含了兩個最基本的方面:一是語言關(guān)注的基本元素/語義;另一個是從基本元素/語義到復合元素/語義的構(gòu)造規(guī)則。在C、C++、Java、C#、Python等通用語言中,語言的基本元素/語義往往離問題域較遠,通過API庫的形式進行層層抽象是降低問題難度最常用的方法。比如,在C語言中最常見的方式是提供函數(shù)庫來封裝復雜邏輯,方便外部調(diào)用。(成都網(wǎng)站制作)
不過普通的API設(shè)計方法存在一種天然的陷阱,那就是不管怎樣封裝,大過程雖然比小過程抽象層次更高,但本質(zhì)上還是過程,受到過程語義的制約。也就是說,通過基本元素/語義構(gòu)造更高級抽象元素/語義的時候,語言的構(gòu)造規(guī)則很大程度上限制了抽象的維度,我們很難跳出這個維度去,甚至可能根本意識不到這個限制。而SQL、HTML、CSS、make等DSL(領(lǐng)域特定語言)的抽象維度是為特定領(lǐng)域量身定做的,從這些抽象角度看問題往往最為簡單,所以DSL在解決其特定領(lǐng)域的問題時比通用程序設(shè)計語言更加方便。通常,SQL等非通用語言被稱為外部DSL(External DSL);在通用語言中,我們其實也可以在一定程度上突破語言構(gòu)造規(guī)則的抽象維度限制,定義內(nèi)部DSL(Internal DSL)。
本文將介紹一種被稱為流暢接口(Fluent Interface)的內(nèi)部DSL設(shè)計方法。Wikipedia上Fluent Interface的定義是:
A fluent interface (as first coined by Eric Evans and Martin Fowler) is an implementation of an object oriented API that aims to provide for more readable code. A fluent interface is normally implemented by using method chaining to relay the instruction context of a subsequent call (but a fluent interface entails more than just method chaining)。下面將分4個部分來逐步說明流暢接口在構(gòu)造內(nèi)部DSL中的典型應用。
1.基本語義抽象
如果要輸出0..4這5個數(shù),我們一般會首先想到類似這樣的代碼: //Java for(inti=0;i<5;++i){ system.out.println(i); } 而Ruby雖然也支持類似的for循環(huán),但最簡單的是下面這樣的實現(xiàn): //Ruby.times{|i|putsi}
Ruby中一切皆對象,5是Fixnum類的實例,times是Fixnum的一個方法,它接受一個block參數(shù)。相比for循環(huán)實現(xiàn),Ruby 的times方式更簡潔,可讀性更強,但熟悉OOP的朋友可能會有疑問,times是否應該作為整型類的方法呢?在OOP中,方法調(diào)用通常代表了向?qū)ο蟀l(fā)送消息,改變或查詢對象的狀態(tài),times方法顯然不是對整型對象狀態(tài)的查詢和修改。如果你是Ruby的設(shè)計者,你會把times方法放入Fixnum類嗎?如果答案是否定的,那么Ruby的這種設(shè)計本質(zhì)上代表了什么呢?實際上,這里的times雖然只是一個普通的類方法,但它的目的卻與普通意義上的類方法不同,它的語義實際上類似于for循環(huán)這樣的語言基本語義,可以被視為一種自定義的基本語義。times的語義從一定程度上跳出了類方法的框框,向問題域邁進了一步!
另一個例子來自Eric Evans的“用兩個時間點構(gòu)造一個時間段對象”,普通設(shè)計: 3//Java TimePointfiveOClock,sixOClock; TimeIntervalmeetingTime=newTimeInterval(fiveOClock,sixOClock); 另一種Evans的設(shè)計是這樣: 2//Java TimeIntervalmeetingTime=fiveOClock.until(sixOClock);
按傳統(tǒng)OO設(shè)計,until方法本不應出現(xiàn)在TimePoint類中,這里TimePoint類的until方法同樣代表了一種自定義的基本語義,使得表達時間域的問題更加自然。
雖然上面的兩個簡單例子和普通設(shè)計相比看不出太大的優(yōu)勢,但它卻為我們理解流暢接口打下了基礎(chǔ)。重要的是應該體會到它們從一定程度上跳出了語言基本抽象機制的束縛,我們不應該再用類職責劃分、迪米特法則(Law of Demeter)等OO設(shè)計原則來看待它們。
2.管道抽象
在Shell中,我們可以通過管道將一系列的小命令組合在一起實現(xiàn)復雜的功能。管道中流動的是單一類型的文本流,計算過程就是從輸入流到輸出流的變換過程,每個命令是對文本流的一次變換作用,通過管道將作用疊加起來。在Shell中,很多時候我們只需要一句話就能完成log統(tǒng)計這樣的中小規(guī)模問題。和其他抽象機制相比,管道的優(yōu)美在于無嵌套。比如下面這段C程序,由于嵌套層次較深,不容易一下子理解清楚: 2//Cmin(max(min(max(a,b),c),d),e)
而用管道來表達同樣的功能則清晰得多: 2#!/bin/bash maxab|minc|maxd|mine我們很容易理解這段程序表達的意思是:先求a,b的大值;再把結(jié)果和c取最小值;再把結(jié)果和d求大值;再把結(jié)果和e求最小值。
jQuery的鏈式調(diào)用設(shè)計也具有管道的風格,方法鏈上流動的是同一類型的jQuery對象,每一步方法調(diào)用是對對象的一次作用,整個方法鏈將各個方法的作用疊加起來。 2//Javascript $('li').filter(':event').css('background-color','red');3.層次結(jié)構(gòu)抽象
除了管道這種“線性”結(jié)構(gòu)外,流暢接口還可用于構(gòu)造層次結(jié)構(gòu)抽象。比如,用Javascript動態(tài)創(chuàng)建創(chuàng)建下面的HTML片段:<divid="’product_123’"class="’product’"><imgsrc="’preview_123.jpg’"alt=""/>._div();
和Javascript的標準DOM API相比,上面的API設(shè)計不再局限于孤立地看待某一個方法,而是考慮了它們在解決問題時的組合使用,所以代碼的表現(xiàn)形式特別貼近問題的本質(zhì)。這樣的代碼是自解釋的(self-explanatory)在可讀性方面要明顯勝于DOM API,這相當于定義了一種類似于HTML的內(nèi)部DSL,它擁有自己的語義和語法。需要特別注意的是,上面的層次結(jié)構(gòu)抽象和管道抽象有著本質(zhì)的不同,管道抽象的方法鏈上通常是同一對象的連續(xù)傳遞,而層次抽象中方法鏈上的對象卻在隨著層次的變化而變化。此為,我們可以把業(yè)務規(guī)則也表達在流暢接口中,比如上面的例子中,body()不能包含在div()返回的對象中,div().body()將拋出”body方法不存在”異常。(高端網(wǎng)站建設(shè))
4.異步抽象
流暢接口不僅可以構(gòu)造復雜的層次抽象,還可以用于構(gòu)造異步抽象。在基于回調(diào)機制的異步模式中,多個異步調(diào)用的同步和嵌套問題是使用異步的難點所在。有時一個稍復雜的調(diào)用和同步關(guān)系會導致代碼充滿了復雜的同步檢查和層層回調(diào),難以理解和維護。這個問題從本質(zhì)上講和上面HTML的例子一樣,是由于多數(shù)通用語言并未把異步作為基本元素/語義,許多異步實現(xiàn)模式是向語言的妥協(xié)。針對這個問題,我用Javascript編寫了一個基于流暢接口的異步DSL,示例代碼如下: //Javascript $.begin() .async(newTask('task1'),'task1') .async(newTask('task2'),'task2') .async(newTask('task3'),'task3') .when() .each_done(function(name,result){ console.log(name+':'+result);}) .all_done(function(){console.log('good,allcompleted');}) .timeout(function(){ console.log('timeout!!'); $.begin() .async(newTask('task4'),'task4') .when() .each_done(function(name,result){ console.log(name+':'+result);}) .end();} ,3000).end();
上面的代碼只是一句Javascript調(diào)用,但從另一個角度看它卻像一段描述異步調(diào)用的DSL程序。它通過流暢接口定義了begin when end的語法結(jié)構(gòu),begin后面跟的是啟動異步調(diào)用的代碼;when后面是異步結(jié)果處理,可以選擇each_done, all_done, timeout中的一種或多種。而begin when end結(jié)構(gòu)本身是可以嵌套的,比如上面的代碼在timeout處理分支中就包含了另一個begin when end結(jié)構(gòu)。通過這個DSL,我們可以比基于回調(diào)的方式更好地表達異步調(diào)用的同步和嵌套關(guān)系。
網(wǎng)頁題目:[成都網(wǎng)站制作]API設(shè)計新思維:用流暢接口構(gòu)造內(nèi)部DSL
文章轉(zhuǎn)載:http://www.rwnh.cn/news/227509.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、電子商務、品牌網(wǎng)站設(shè)計、建站公司、小程序開發(fā)、App開發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容