|
精华帖 (0) :: 良好帖 (10) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
|---|---|
| 作者 | 正文 |
|
时间:2008-04-29 关键字: 编程范式, 情景编程
冒号和他的学生们——程序员提高班纪事
11 .编程范式:情景编程 理论是认生的孩童,陪他多玩玩,自会活泼起来 ——题记
叹号摘下眼镜,揉了揉眼:“范式再好,多了也难免有些审美疲劳。” 逗号也搓着太阳穴:“现在脑子被灌得沉甸甸的。” “彼此彼此!你们的脑袋闹涝灾,我的喉咙闹旱灾。”冒号说着,拿起矿泉水瓶一饮而尽。 大伙听着怪别扭的,这不是拐着弯说我们脑子进水了吗? 冒号清了清嗓子:“为尊重民意,也为避免消化不良,大家先放松一下。下面我们来个情景编程 。” “情景编程?没听说过,只听说过情景英语。”问号觉得新鲜。 “都是学语言嘛,有何两样?”冒号轻描淡写,“让我们试着用生活中的实例将一些编程范式串联起来。前面提到, 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。” 下面开始有些骚动,大家早已脑中满满而腹中空空,有些头重脚轻了。 冒号见状,遂发出激动人心的号召:“今天的课到此结束,让我们从虚拟的餐馆中走出,到真实的餐馆中去吧。” 众人齐声欢呼。
声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
| 返回顶楼 | |
浏览 204 次


