<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    冒號課堂§4.4:情景范式

     

    冒號課堂

    第四課 重溫范式(4)


    4.4
    情景范式——餐館里的編程范式

    理論是認生的孩童,多陪他玩玩,自會活潑起來                             ——題記

     

    關鍵詞:      編程范式,情景編程

    摘要:   編程范式在餐館中的應用

     

      ?提問

     

    l        什么是閉包?為什么被稱為閉包?它有什么作用?

    l        規則引擎有何用處?

    l        能否設想一個生活中的場景,把介紹的九種編程范式都用上?

       
    講解


    嘆號摘下眼鏡,揉了揉眼:“范式再好,多了也難免有些審美疲勞。”

    逗號也搓著太陽穴:“這段時間腦子被灌得沉甸甸的。”

    “彼此彼此!你們的腦袋老鬧澇災,我的喉嚨老鬧旱災。”冒號說著,拿起礦泉水瓶一飲而盡。

    大伙聽著怪別扭的,這不是拐著彎說我們腦子進水了嗎?

    冒號清了清嗓子:“為尊重民意,也為避免消化不良,大家先輕松一下。下面我們來個情景編程。”

    “情景編程?沒聽說過,只聽說過情景英語。”問號感到挺新鮮。

    “都是學語言嘛,有何兩樣?”冒號輕描淡寫,“讓我們試著用生活中的實例將一些編程范式串聯起來。前面提到,OOP可以看作管理一個服務型公司,現在以餐館為例,你們每人設計一類對象及其提供的服務。”

    問號來了興致:“我先來吧。構造一個前臺接待員,負責迎客、引座、送客。”

    句號很是不滿:“還真不客氣,上來就把最漂亮的對象搶走了。”

    臺下一陣笑聲。

    “我來構建最常見的服務員。”逗號一捋袖子,一副準備開干的樣子,“負責斟茶、寫菜、上菜、換盤。”

    “嗯,很熟練。”冒號一本正經。

    句號實在得很:“我設計收銀員,專管收帳、出具發票。”

    引號頗為自豪:“我造一個技術含量最高的大廚,專門負責烹調。”

    逗號不服:“你倒簡單,那么高的技術含量,敢情炒肉和燉肉一個做法???”

    引號自覺理虧:“那就負責蒸、煮、炒、燉吧。”

    冒號為其辯護:“引號同學并沒有錯,可惜沒能堅持。廚師只需提供一種服務:把紙上菜變成盤中菜,至于蒸、煮、炒、燉等具體做法純屬實現細節。”

    嘆號有點委屈:“唉,看來我只好做技術含量最低的廚工了,負責食品預加工、洗碗、打掃清潔。”

    冒號將大家設計的類翻譯成Java代碼——

    // 前臺接待員

    Class Receptionist

    {

    public void receive(Customer customer)        {…} // 迎客

    public void usher(Customer customer) {…} // 引座

    public void send(Customer customer)   {…} // 送客

    }

    // 服務員

    Class Waiter

    {

    public void pourTea(Customer customer)       {…} // 斟茶

    public List<Order> write(Customer customer){…} // 寫菜

    public void serve(Customer customer, Course course){…} // 上菜

    public void exchangePlate(Customer customer) {…} // 換盤

    }

    // 收銀員

    Class Cashier

    {

    public void charge(Customer customer)       {…} // 收帳

    public void issueInvoice(Customer customer){…} // 出具發票

    }

    // 廚師

    Class Cook

    {

    public Course cook(Order order)           {…} // 烹調

    }

    // 廚工

    Class KitchenHand

    {

    public void prepareFood()                {…} // 準備食品

    public void washDishes()                {…} // 洗碗

    public void clean()                            {…} // 打掃清潔

    }

    “你們造人,我來造物。”冒號構造了一個餐館的類——

    // 餐館

    Class Restaurant

    {

    // 每當有顧客來訪,返回該顧客

    private Customer accept() {…}

     

    // 為指定顧客提供所有的餐館服務

    private void serve(Customer customer) {…}

    // 餐館服務

    public void service()

    {

           while (true) // 無限循環,假設餐館7×24小時營業

           {

                   final Customer customer;

                   if ((customer = accept() ) != null) // 某顧客來訪

                   {

                          serve(customer); // 為該顧客提供服務

    }

    }

    }

    }

    冒號解說道:“這里accept類似Socketaccept,屬于堵塞呼叫,意味著此方法將堵塞進程直至收到新數據。為簡單計,把一行顧客當作一個Customer。大家對此段代碼有何看法?”

    “沒什么,很簡單啊。”逗號說完補充一句,“關鍵是serve方法的實現。”

    “這里我們明顯用到了兩個范式,對象式過程式。”冒號提示道。

    引號會意:“應該還需要并發式。serve如果與service在同一線程中運行,那么餐館只有等服務完一個Customer后才能服務后面的,這顯然是荒唐的。”

    “對極了!”冒號將“serve(customer);”改寫為——

    // serve(customer);  // 錯誤地使用單線程!

    new Thread              // 構造一個線程

    (new Runnable()

    {

            public void run(){ Restaurant.this.serve(customer); }

    }).start();         // 啟動該線程

    冒號道:“這回serve在新線程中運行,不會耽誤Restaurant服務下一位Customer了。”

    問號眼尖:“我注意到聲明customer時前面加上了關鍵字final,有必要嗎?”

    “如果不用線程,是不必要的。”冒號回應道,“我們在建造線程時用到了實現Runnable接口的匿名類anonymous class),它是涉及到局部變量customer內部類inner class),Java語法要求該局部變量必須是final類型。值得一提的是,這里不僅用到了并發式,而且與函數式也密切相關。”

    “函數式?”逗號奇道。

    “不錯。”冒號堅定地點著頭,“我們不是提過函數式中的函數是頭等公民嗎?也就是說,函數與其他基本數據類型一樣,可以作為傳遞參數、返回值或與變量名綁定。閉包closure)便是這樣一種函數,并且還能保留當初創建時周圍的環境變量。以上匿名類本質上是函數serve的包裝,經實例化后作為參數傳入Thread的構造函數,并且記住了外部類的局部變量customer——這也是為什么它必須是final以保證不被重新賦值的原因。應該說這是一種OO化的閉包形式,預計在Java 7中它的用法會更簡潔,這也意味著Java代碼中將飄進更多的函數式風味。”

    引號喃喃道:“以前只聽說過數學里有個閉包的概念。”

    冒號略加指點:“可以這么理解:所謂,指函數與其周圍的環境變量捆綁打包;所謂,指這些變量是封閉的,只能為該函數所專用。如果你真懂數學,就會發現它們本質上是相通的。”

    嘆號隱隱約約地覺得:“把函數與變量捆綁起來,怎么聽起來像是OOP?。?#8221;

    “嗯,的確相似。”冒號微頷,“不妨認為閉包就是封裝了環境變量的隱形對象的方法——通常是匿名方法。我們用一段JavaScript代碼來加深印象——”

    /* 返回函數f的近似導函數 */

    function derivative(f)

    { // dx最好作為參數傳入,放在此處主要是為了說明閉包的用法
           var dx = 0.00001;     // dx越小,近似度越高
           return function(x) { return (f(x + dx) - f(x)) / dx; };

    }

    /* 返回一個數的平方數 */

    function square(x) { return x * x; }

    /* 返回一個數的雙倍數 */

    function double(x) { return 2 * x; }

    /* 對任何一個不大的數值變量x(比如小于100),下列函數的返回值應該非常接近于零 */

    function testSquareDerivative(x) { return derivative(square) (x) - double(x); }

    嘆號感到有點頭痛:“怎么跑出了微積分?大學學的那點高數早就還給老師了。”

    冒號笑著安慰他:“還給老師沒關系,我再借給你一點:平方函數的導數是雙倍函數。因此,函數derivative(square)應該很接近函數double的作用效果。作為檢驗,testSquareDerivative能將任何一個不大的數映射到近似于零的數[1]。”

    引號這下徹底明白了:“在求導函數derivative中,傳入的參數f和返回值都是函數,這是函數作為頭等公民的特征。其中返回的匿名函數就是閉包,它附著了兩個環境變量:外層函數的傳入參數f和局部變量dx。”

    “完全正確!”冒號作出積極的肯定,“如果不是閉包,這兩個環境變量在derivative返回后就會失去效用。由此可見,合理地使用閉包能使代碼更加簡潔清晰,散發出函數式特有的優雅氣質。”

    句號有些按捺不住編程的沖動,自告奮勇:“我來具體實現餐館的serve方法吧。”

    得到冒號的默許,句號在黑板上寫下——

    private void serve(Customer customer)

    {

     // 找一個空閑的接待員

    Receptionist receptionist = findReceptionist();

    receptionist.receive(customer);

    receptionist.usher(customer);

                // 找一個空閑的服務員

    Waiter waiter = findWaiter();

    waiter.pourTea(customer);

    List<Order> orders = waiter.write(customer)       ;

    // 將菜單交給一位廚師

    Cook cook = waiter.pass(orders);

    for (Order order : orders) // 廚師照單做菜

    {

     Course course = cook.cook(order);

                // 找一個空閑的服務員

    waiter = findWaiter();

    // 服務員上菜

    waiter.serve(customer, course);

    // 顧客開始享用

    customer.eat(course);

    }

    // 顧客用餐完畢。。。

          // 找一個空閑的收銀員

    Cashier cashier = findCashier();

    cashier.charge(customer);

    cashier.issueInvoice(customer);

     // 找一個空閑的接待員

    receptionist = findReceptionist();

    receptionist.send(customer);

    }

    句號寫畢又復查一遍,拍拍手上的粉筆灰,心滿意足地走下臺來。

    嘆號提意見:“我的廚工沒派上用場,應該在廚師烹調前調用KitchenHandprepareFood方法。”

    問號挑出另外的毛?。?#8220;在for循環中,廚師、服務員和顧客的行為應該在不同的線程中,廚師不可能等服務員上完一道菜或顧客吃完一道菜后才做下一道。”

    “可能更復雜呢!”逗號也來湊熱鬧,“一位顧客點的幾樣菜可能分別由幾位廚師同時做,每位廚師都在不同的線程中工作。”

    引號更嚴謹:“還應有一個后臺線程,讓服務員(Waiter)隨時換盤(exchangePlate),讓廚工(KitchenHand)隨時洗盤(washDishes)和清潔(clean),這樣所有服務人員提供的服務都用上了。”

    句號倒抽涼氣:“估不到漏洞這么多,并發式真是無處不在啊。”

    冒號繼續點撥:“換盤子有兩種方式:一種是服務員主動換,一種是客人要求換。前者是輪詢,后者是通知。”

    “哦,事件驅動式!”句號迅即反應過來,“客人是事件源,服務員是事件處理器,客人不定期地招手呼喚是在發表事件以通知服務員。客人與服務員是多對多的松耦合關系。”

    冒號點點頭,又指著引號:“剛才有人不滿你的大廚職責過于簡單,現在你來實現一下,也好顯顯技術含量。”

    引號在臺上摸了半天頭,編出一段代碼——

    Class Cook

    {

    public Course cook(Order order)

    {

     // 根據菜單查食譜

    Recipe recipe = lookupRecipe(order);

    // 找到食譜的烹調步驟

    List<Instruction> instructions = recipe.getInstructions();

    for (Instruction instruction : instructions)

    {

        follow(instruction); // 按食譜的指令操作

    }

    }

    }

    “堂堂大廚原來是靠查食譜做菜的。”逗號揶揄道。

    引號為難地說:“這不是在編程嘛,好端端的人腦,不得不去模擬電腦,完全搞倒了。”

    “要設計會烹調的機器人,興許還真得這樣呢。”冒號笑道,“不過由于各種菜式組合繁多,如果每種菜都配菜譜未免太龐雜,如何精簡呢?”

    句號建議:“菜式成千上萬,烹調技法相對少許多,不妨以技法為主線。”

    “好主意!”冒號挑起大拇指,“如果把待加工的菜看作數據,技法看作算法,將數據與算法分離,以算法為中心,那是什么范式?”

    泛型式!”大家異口同聲。

     “至此我們已涉及了過程式、對象式、并發式、函數式、事件驅動式和泛型式。”引號扳著手指算著,“還差邏輯式、元編程和切面式了。”

    冒號把目光轉向逗號:“寫菜單并不容易,如果客人不直接點菜,你的服務員如何向他推薦?”

    逗號答:“最簡單的方法是報菜名,并一一詢問客人。”

    冒號皺眉:“這樣你是簡單了:一個迭代就完事,可客人也該發火了。”

    逗號趕緊修正:“先詢問客人的口味、忌諱等等,再向他建議一些菜式。”

    “這還差不多。”冒號眉頭舒展開來,“考慮到客人的口味、忌諱等各有不同,餐館的菜單也隨時可能變化,如果把這些都硬編碼(hardcode),再加上層層疊疊的if-else語句,代碼將成為懶婆娘的裹腳——又臭又長又難維護。”

    引號提議:“可以把這些信息預先存入數據庫,屆時用SQL查詢。”

    “想法很好,只是有一點難度。”冒號提醒道, “這些信息并非簡單的對應關系,包含一些邏輯推理,甚至需要一些模糊判斷。”

    句號一拍大腿:“前面不是提到領域特定語言DSL嗎?將所有規則用自定義的DSL編寫,再利用元編程轉換成C、Java之類的通用語言,不是很好嗎?”

    “棒極了!”冒號不吝贊詞,“不過還有一種思路。我們可以搜集餐館的菜式、顧客口味、忌諱以及各種菜與口味、忌諱之間的關系等等一系列事實和規則,用規則語言Rule Language)來描述,通過規則引擎Rule Engine)來導出符合顧客需求的菜肴。這種方式將業務規則與應用程序分離、將知識表示與邏輯實現分離,是SoC原理的一種應用,同時也是一種邏輯式編程。”

    問號關心地問:“這些規則引擎與Java程序兼容嗎?”

    冒號回答:“不少規則引擎用Java實現或專為Java平臺設計,如Jess、Drools、JLisa、JRules等,另外Sun還發布了javax.rules API (JSR 94)以統一對各類引擎的訪問接口。”

    引號頗感意外:“既然是邏輯式編程,為什么不采用代表語言Prolog呢?”

    冒號準備了一大段理由等著他:“剛才提到的規則引擎都是基于Rete算法[2]的,主要采用數據驅動的(data-driven正向推理forward chaining)法,而Prolog引擎采用目標驅動的(goal-driven逆向推理backward chaining)法。正向推理自底向上,利用推理規則從已有的事實數據推出更多的數據,直到達成目標;逆向推理正相反,自頂向下,從目標出發尋找滿足結論的事實[3]。相比而言,正向推理適合針對不同輸入作出不同反應,而逆向推理適合回答查詢?,F在是服務員根據客人的喜好提建議,當然用正向推理更合適。再說這類引擎與Java的集成更加方便,因此我們沒有選擇Prolog。”

    講到此處,每個人都意識到,只剩下最后一個范式了。

    冒號提出一個新問題:“假如餐館經理接到顧客投訴,反映服務人員態度不好,衛生狀況也不理想,應該怎么辦?”

    問號搶先說:“首先我的接待員在迎客(receive)時要笑容可掬地對顧客說:‘歡迎光臨!’,在送客(send)時要對顧客鞠躬:‘請慢走,歡迎下次再來’”

    逗號接著說:“我的服務員在上完菜后應對客人說:‘請慢用’,句號的收銀員也應加些禮貌用語,讓人家高高興興地掏錢。”

    句號補充道:“服務員在上菜(serve)前、廚師在烹飪(cook)前應洗手,廚工在洗碗(washDishes)后應對餐具消毒。”

    冒號緊接著問:“如果餐館對禮貌規范或衛生標準做修改,必然要牽扯不同類中的不同的方法,維護起來很不方便,怎樣才能有效地解決這個問題呢?”

    答案已經昭然若揭了。

    冒號干脆自問自答:“不錯,正是用切面式編程。只要創立兩個AspectEtiquetteSanitation,分別負責禮貌規范和衛生標準方面的事務。一旦某一方面的要求發生變化,比如餐館來了外賓,或者碰上非典或禽流感,只需在相應的Aspect模塊中作調整:將禮貌用語換成英語或者提高衛生標準等等。如果采用runtime AOP,甚至還可在運行期選擇激活或禁用這些Aspect。”

    下面開始有些騷動,大伙早已腦中滿滿而腹中空空,有點頭重腳輕了。

    冒號見狀,遂發出激動人心的號召:“今天的課到此結束,讓我們從虛擬的餐館中走出,到真實的餐館中去吧!”

    眾人齊聲歡呼。


      插語


    [1] 若輸入數過大,則需要設定更小的dx。此外,還可能產生計算溢出。

    [2] Rete算法是一種高效的模式匹配算法,用于實現規則生成系統(production rule system

    [3] 用邏輯的語言來說,正向推理順著從前件(即if語句)到后件(即then語句)的方向,逆向推理順著從后件到前件的方向。

      
     總結


    l        閉包是一種能保留當初創建時的環境變量的函數。它通常以匿名的方式存在,多用于函數式編程中,能使代碼更加簡潔清晰。Java中的匿名類可以看作OO化的閉包形式。

    l        Jess、DroolsJLisa、JRules等規則引擎主要基于正向推理,能無縫地與Java平臺集成。它們提供了邏輯式編程環境,能有效地將業務規則從應用程序中分離出來,提高了軟件的靈活性和可維護性。

    l        每種編程范式都能在生活中找到它的應用,它們本來就是人類思維方式的投影。

      

     “”參考


    [1] WikipediaClosure (computer science)http://en.wikipedia.org/wiki/Closure_(computer_science)

    [2] Ernest Friedman-HillJess 7.1p2 manualhttp://www.jessrules.com/jess/docs/Jess71p2.pdf

    [3] Mark Proctor等.Drools Documentationhttp://downloads.jboss.com/drools/docs/4.0.7.19894.GA/html_single/index.html

     

    課后思考

    l      同樣一個問題用不同的語言來編程,代碼可能會有極大的差異。你認為這種差異的主要根源是語言還是范式?

    l      認真研究本課中的編程范式匯總表,并補充新的內容,如各范式的關鍵詞、理論基礎、最佳實踐、注意事項等等。

    l      掌握編程范式對語言學習和編程設計有何實際意義?

    posted on 2008-12-21 23:18 鄭暉 閱讀(3106) 評論(1)  編輯  收藏 所屬分類: 冒號課堂

    評論

    # re: 冒號課堂§4.4:情景范式 2008-12-22 09:52 貓團長

    實在是精辟,頂!  回復  更多評論   

    導航

    統計

    公告

    博客搬家:http://blog.zhenghui.org
    《冒號課堂》一書于2009年10月上市,詳情請見
    冒號課堂

    留言簿(17)

    隨筆分類(61)

    隨筆檔案(61)

    文章分類(1)

    文章檔案(1)

    最新隨筆

    積分與排名

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 日韩内射激情视频在线播放免费| 青春禁区视频在线观看直播免费| 日韩精品一区二区亚洲AV观看| 四虎成年永久免费网站| 成人亚洲国产精品久久| 另类专区另类专区亚洲| 国产V亚洲V天堂无码久久久| 无码区日韩特区永久免费系列| 美女又黄又免费的视频| 夜夜亚洲天天久久| 国产三级免费电影| 91精品国产免费久久国语蜜臀| 久久亚洲色WWW成人欧美| 久久久无码精品亚洲日韩按摩| 全部免费a级毛片| 999久久久免费精品国产| EEUSS影院WWW在线观看免费| 亚洲国产成人久久综合一区| 亚洲一区二区三区无码影院| 欧美三级在线电影免费| 免费在线看黄网站| 成人国产网站v片免费观看| 亚洲小说图片视频| 国产av无码专区亚洲av桃花庵| 精品国产麻豆免费网站| 最近最新高清免费中文字幕| 国产99久久久国产精免费| 亚洲日韩国产一区二区三区在线 | 成人在线免费视频| 亚洲av成人一区二区三区| 亚洲人成人77777网站| 亚洲成a人片在线观看老师| 成人AV免费网址在线观看| 久久久久久久99精品免费观看| 亚洲av中文无码乱人伦在线观看 | 狠狠躁狠狠爱免费视频无码| 亚洲人成网亚洲欧洲无码| 亚洲第一页在线播放| 亚洲AV永久无码精品成人| 国产亚洲日韩在线三区| 免费jjzz在线播放国产|