程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 冒號和他的學生們(連載12)——情景范式

冒號和他的學生們(連載12)——情景范式

編輯:關於JAVA

12.情景范式

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

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

逗號也搓著太陽穴:“現在腦子被灌得沉甸甸的。”

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

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

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

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

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

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

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

台下一陣笑聲。

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

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

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

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

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

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

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

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

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

// 前台接待員
    Class Receptionist
    {
        public void receive(Customer)        {…} // 迎客
        public void usher(Customer)          {…} // 引座
        public void send(Customer)           {…} // 送客
    }
    // 服務員
    Class Waiter
    {
        public void pourTea(Customer)        {…} // 斟茶
        public List<Order> write(Customer)   {…} // 寫菜
        public void serve(Customer, Course)  {…} // 上菜
        public void exchangePlate(Customer)  {…} // 換盤
    }
    // 收銀員
    Class Cashier
    {
        public void charge(Customer)         {…} // 收帳
        public void issueInvoice(Customer)   {…} // 出具發票
    }
    // 廚師
    Class Cook
    {
        public Course cook(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類似Socket的accept,屬於堵塞呼叫(blocking call),意味著此方法將堵塞進程直至收到新數據。為簡單計,把一行顧客當作一個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類型。值得一提的是,這裡不僅用到了並發式,而且與函數式也密切相關。”

“函數式?”逗號奇道。

“不錯。”冒號堅定地點著頭,“函數式的一個重要特征是:函數是頭等公民(first-class citizen),即與其他基本數據類型一樣,可以作為傳遞參數、作為其他函數返回值或與變量名綁定。閉包(closure)便是這樣一種函數,並且能保留當初創建時周圍的環境變量。以上匿名類本質上是函數serve的包裝,經實例化後作為參數傳入Thread的構造函數,並且記住了外部類的局部變量customer——這也是為什麼它必須是final以保證不被重新賦值的原因。應該說這是一種OO化的閉包形式,預計在Java 7中它的用法會更簡潔。”

句號自告奮勇:“我來具體實現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);
    }

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

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

問號挑出另外的毛病:“在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),代碼將成為懶婆娘的裹腳——又臭又長又難維護。”

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

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

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

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

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

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

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

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

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

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

句號補充道:“服務員在serve前、廚師在cook前應洗手,廚工在washDishes後應對餐具消毒。”

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

答案已經昭然若揭了。

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

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

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

眾人齊聲歡呼。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved