Android应用框架原理与程序设计(简中版)


1 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 应用框架原理与程序设计 36 技 适用于 Android 1.0 版 本书完整范例程序代码请到网站下载: www.misoo1.com 或 tom-kao.blogspot.com 高焕堂 着 (2008 年 10 月第三版) misoo.tw@gmail.com PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 2 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 著作权声明: 本书已于 2008 年 4 月出版发行。 著作权属于高焕堂所拥有。 本 e-book 可整份免费自由复制流传。 但非经作者书面同意,不可以加以切割、剪辑及部分流传。 任何商业用途皆需得到作者的书面同意。 书内范例原始程序代码,请到 tom-kao.blogspot.com 或www.misoo1.com下载。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 3 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第三版序言 由于 Android 正式(1.0)版和 HTC/Android 实体手机皆已经上市了,因之本书 也针对 Android1.0 版的出爐而实时修订,成为本书的第三版。 大家几乎都听过愚公移山的故事,但是大家常把焦点摆在愚公和移山,而 忽 略了畚「箕」的角色。禮记.学记篇上有言:良弓之子,必学为箕。其意思 是,欲做出优良的弓,必先好好研究其模子(即箕)。最近许多人知道 Google 推出轰动武 林、惊动万教的 Android 手机平台。但是几乎都只关心如何在该 新平台上开发应用程序,却忽略了 Android 是个框架(Framework),而框架裡 含有成百上千个「箕」 類(注:基類是大陸对 SuperClass 的译词)。基于「良弓之 子,必学为箕」的精神,本书先教您正确认識框架(箕)之原理,然后才介绍如 何善用畚箕來开发出优良的 Android 应用程序(良弓)。本书共分为 4 篇: ※ 第一篇:介绍应用框架概念、原理和特性。 ※ 第二篇:阐述应用框架之设计技巧。亦即,如何打造应用框架。 (注:如果你的职务是「 使用」Android 框架來开发应用程序的 话,可以跳过本篇,直接进入第三篇。) ※ 第三篇:說明及演練 Android 应用程序设计的 36 技。 ※ 第四篇:介绍 Android 框架与硬件之间 C 组件的开发流程及工具。 笔者并不是說 Android 的应用程序员是愚公,而旨在說明手机软件領域的三个 主要分工角色: 做畚箕者:如 Andriod 开发团队。 畚箕买主:如 Google 公司。 挑畚箕者:如 Android 应用程序员。 本书也不把您设定为应用程序员单一角色,而是盼望能协助您开拓更宽广 的未來,无論在上述的任何角色,都能如鱼得水,辉煌腾达。于此诚挚地祝福您! 高焕堂 谨識于 2008.10.3 tom-kao.blogspot.com PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 4 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 目 錄 第一篇 良弓之子,必学为箕(框架) ~禮记.学记~ 第 1 章认識应用框架,14 1.1 何谓应用框架 1.2 框架的起源 1.3 框架的分层 1.4 框架的「无用之用」效果 1.5 框架与 OS 之关系:常見的迷思 第 2 章应用框架魅力的泉源:反向沟通, 31 2.1 前言 2.2 认識反向沟通 2.3 主控者是框架,而不是应用程序 2.4 现代应用框架:采取广义 IoC 观念 2.5 框架的重要功能:提供预设行为 第二篇 无之(抽象)以为用 ~老子:无之以为用~ 第 3 章如何打造应用框架, 54 3.1 基础手艺:抽象(无之)与衍生(有之) 3.2 打造框架:细腻的抽象步骤 3.2.1 基本步骤 3.2.2 细腻的手艺(一):比较资料成员 3.2.3 细腻的手艺(二):比较函數成员 3.2.4 细腻的手艺(三):将抽象類别转为接口 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 5 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第三篇 有之(继承)以为利 ~老子:有之以为利~ 第 4 章 应用程序设计的基础手艺 12 技, 82 4.1 #1:如何建立 Menu 选单 4.2 #2:如何呈现按钮(Button)之 1 4.3 #3:如何呈现按钮(Button)之 2 4.4 #4:如何进行画面布局(Layout) 4.5 #5:如何呈现 List 选单之 1 4.6 #6:如何呈现 List 选单之 2 4.7 #7:如何运用相对布局(RelativeLayout) 4.8 #8:如何运用表格布局(TableLayout) 4.9 #9:如何动态变换布局 4.10 #10:如何定义自己的 View 4.11 #11:如何定义一组 RadioButton 4.12 #12:一 个 Activity 启动另一个 Activity 第 5 章 UseCase 分析与画面布局之规划, 141 5.1 善用 UseCase 分析 5.2 以 Android 实践 Use Case 分析之策略 第 6 章 UseCase 分析的实践(策略-A):6 技, 149 6.1 #13:使用 Menu 和 starActivity()实践之 6.2 #14:使用 starActivityForResult()替代 startActivity() 6.3 #15:使用 ListView 替代 Menu 6.4 #16:以 ListActivity 替代 Activity 父類别 6.5 #17:改由.xml 档案定义画面布局 6.6 #18:使用 onResume()函數 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 6 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第 7 章UseCase 分析的实践(策略-B):2 技, 179 7.1 #19:一个 Activity 支持兩个画面布局 7.2 #20:将兩个画面布局合并为一 第 8 章介绍关聯式资料库与 SQLite,193 8.1 何谓关聯式资料库 8.2 建立一个表格(Table) 8.3 从表格中查询资料 8.4 关聯资料模型 8.5 关聯的种類 8.6 兩个表格之互相聯结 8.7 SQL 子句:加总及平均 8.8 SQL 子句:分组 第 9 章资料库手艺:5 技, 201 9.1 #21:SQLite 基本操作 9.2 #22:让 SQLite 披上 ContentProvider 的外衣 9.3 #23:细說 SQLite 与 ContentProvider 9.4 #24:让 SQLite 配合 onCreate()、onResume()而來去自如 9.5 #25:如何实现商业交易(Transaction) 第 10 章 进阶手艺 10 技, 237 10.1 #26:如何定义 BroadcastReceiver 子類别 10.2 #27:如何撰写 Service 子類别 10.3 #28:如何使用 ProgressDialog 对象 10.4 #29:如何捕捉按键的 KeyEvent 10.5 #30:善 用 UMLStatechart 严格控制系统的狀态 10.6 #31:如何使用 MapView PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 7 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 10.7 #32:如何使用 WebView 10.8 #33:如何自动化操作画面输入 10.9 #34:如何活 用 COR 设计样式 10.10 #35:如何活用 State 设计样式 第四篇 第三十六技:为箕是上策 第 11 章 如何撰写框架与硬件间之 C 组件, 307 11.1 #36:如何撰写框架与硬件间之 C 组件 11.2 发展 AndroidC 组件的经济意义 附錄 A:327 ◆ A-1 如何安装 Windows 平台的 AndroidSDK1.0 版及 Eclipse ◆ A-2 如何離线安装 AndroidSDK1.0 版及 Eclipse ◆ A-3 如何着手撰写 Android 应用程序 ◆ A-4 如何执行 Android 应用程序 ◆ A-5 如何安装 Linux/Ubuntu 平台的 AndroidSDK1.0 版及 Eclipse ◆ A-6 如何安装 C/C++CrossCompiler 附錄 B:336 ◆ B-1 高焕堂于 Omia 行动应用服务聯盟会议上演讲的讲义 ◆ B-2 欢迎一起推动「百万个小 Google 计划」 ◆ B-3 迎接 IT 第三波:移(行)动时代 ◆ B-4 高焕堂教你最先进的「现代软件分析与设计」 ◆ B-5 认識 Android 仿真器的操作 Eclipse PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 8 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 本书由 Misoo 团队创作与出版 Misoo 技术团队介 绍 由高焕堂領军的 Misoo 团队与大陸、俄羅斯、日本专家所组成的跨国嵌入式聯 合 设计(Co-design)团队。Misoo 的开放合作态度,赢得国际的好感和商机。 位于风景秀麗的 Voronezh, Russia Russia 例如,跨国团队成功地将俄羅斯研发 20 多年的顶级 Linter 嵌入式资料库系统纳 入 Android 手机裡执行,成为 Android 的嫡系成员之一。此外,Misoo 团队开 发的 Android 游戏应用程序也顺利外销欧美诸国,如下图: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 9 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 还有,手机在线游戏等等,如下图: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 10 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 跨国团队经验丰富、技术精湛,嵌入式开发成功经验,包括: 客制化影音播放器(videoplayer)开发 嵌入式资料库管理引擎(DBMS)开发 行动平台 GPS 系统开发 (Blackberry,WinCE,J2ME) 电信业的专属无线协议(wirelessprotocol)的开发 学习内容播放系统开发(Flash-based) PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 11 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 基于 Misoo 的开放精神,高焕堂将本书制作成 e-book 供大家免费阅讀, 希望 本书在这千载难逢的大好机会裡,能陪伴诸位的成长与茁壮。此外,高 焕堂又把 一些跨国团队的实务经验和技术加以编辑,并出版成书,或成为企 业培训课程的 讲义,将进一步与大家分享。 如何与 Misoo 跨国团队技术合作呢? ◎ 开发项目(项目)合作: 欢迎直接与 Misoo 团队聯络: TEL:(02)2739-8367 E-mail: misoo.tw@gmail.com ◎ 公开教育训練课程,或企业团队内训: 台北地区 欢迎与 Misoo 团队聯络: TEL:(02)2739-8367 E-mail: misoo.tw@gmail.com 上海地区 欢迎与 祝成科技洽询: TEL:400-886-0806 E-mail: sv@softcompass.com 欢迎多多指教 Misoo 网页: tom-kao.blogspot.com 或 www.misoo1.com PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 12 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 欢迎报名參加高焕堂 主讲的 GoogleAndroid 技术教育训練课程 详细课纲与日期,请上网 www.misoo1.com 服务电话: (02)2739-8367 E-mail: misoo.tw@gmail.com 高焕堂的第 2 本 Android 畅销书籍(天珑网路书局热卖中) *** 详细目錄 请看第 308 页 或上网www.android1.net *** PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 1 章 认 識 应用框架 13 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第一篇 良弓之子,必学为箕(框架) ~~禮记.学记~~ 良 弓 來 自 好 的框架(箕)。 优良的应用 程序來自美好的应用框架。 优秀的 Android 程序员,必先学习应用框架的原理。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 14 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第 1 章 认識应用框架 1.1 何谓应用框架 1.2 框架的起源 1.3 框架的分层 1.4 框架的「无用之用」效果 1.5 框架与 OS 之关系:常見的迷思 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 1 章 认 識 应用框架 15 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 1.1 何谓应用框架 顾名思义,应用框架是﹕某特定应用領域(Domain)中,程序间的共同结 构。 让该領域中的程序员们,依共同结构來发展程序,使程序间具有一致 性,增加了 程序的清晰度,以降低程序的设计与维护费用。 所谓「共同结构」,包括了通用的類别、对象、函數,及其间的稳定关系。由于框 架 是 通 用的,大家能共享(Share) 之 , 增 加 了 工 作 效 率 , 提 升 了 软 体 师 的 生 产 力 (Productivity)。兹拿个简单例子來說吧﹗兩个长方形,分别为直角及圆角,如下﹕ 首先分辨它们的異同点,然后将其共同部分抽離出來,如下﹕ 我们称这过程为「抽象」 (Abstraction) 。并称此图形为「抽象图」,其只含 共同部分,而相異部分从缺。原有的直角及圆角方形,为完整图形,称为 「具体 图」。一旦有了抽象图,就可重复使用(Reuse) 它來衍生出各种具体 图,且事半功 倍﹗例如﹕ ●用途 1 ── 衍生直角方形。 拷贝一份抽象图,在图之四角分别加上┌、┘、└及┐,就成为 直角 方形了,如下﹕ PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 16 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ●用途 2 ── 衍生圆角方形。 拷贝一份抽象图,在图之四角分别加上╭、╰、╯及╮,就成为 圆 角方形了,如下﹕ ●用途 3 ── 衍生球角方形。 拷贝一份抽象图,在图之四角各加上●就成为﹕ 上述简单例子中, 說明了兩个重要动作﹕ ☆抽象──从相似的事物中,抽離出其共同点,得到了抽象结构。 ☆衍生──以抽象结构为基础,添加些功能,成为具体事物或系统。 同样地,在软件方面,也常做上述动作﹕ ★抽象── 在同領域的程序中,常含有许多類别,这些類别有其共同点。 程序 师将類别之共同结构抽離出來,称为抽象類别(Abstract Class)。 ★衍生── 基于通用结构裡的抽象類别,加添些特殊功能,成为具体類 别,再诞生对象。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 1 章 认 識 应用框架 17 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 所以「抽象類别」存在之目的,是要衍生子類别,而不是由它本身來诞生物 件。由于抽象類别本身不诞生对象,所以有些函數并不完整。反之,如果類别 内 之 函 數 ,皆是完整的,而且要用來诞生对象,就称它为具体類别(Concrete Class)。上述简单例子中,說明了兩个重要动作﹕ ☆抽象──从相似的事物中,抽離出其共同点,得到了抽象框架。 ☆衍生──以抽象框架为基础,添加些功能,成为具体事物。 其中,「抽象」结果的好坏,决定于程序员的領域知識,及其敏锐观察 力。 这是个复杂的动作,其过程愈精细,愈能得到稳定而通用的框架。一旦 有了稳定 且弹性的框架,衍生具体類别的动作,就轻而易举了。 框架中除了抽象類别外,还有類别间之关系。未來衍生出子類别,并诞 生物 件,其对象就会依循既定的关系來沟通、协调与合作。因之,框架說明 了对象的 沟通与组织方式,就如同「食谱」叙述着食物料理方法。 不 过, 食谱并不能完全比喻框架,只比喻一部分而已。食谱只叙述 料理方法,并无真正的葱、牛肉等。然而框架含有類别、函數、以及对 象等真正的程式。因之,有人拿「未插完的花盆」來比喻框架,似乎更传神 ﹗插花老师先插上 背景花,并留下空间,任学生发挥,继续插完成。框架设 计师提供了基本類别, 也预留空间让您发挥,继续衍生出子類别。 从上所述,可知框架包括了﹕ ☆一群抽象類别,類别内有函數,函數内有指令,但有些函數内的指令从 缺, 预留给应用程序员补充之。 ☆抽象類别间之稳定关系。 然而,现在市面上的框架,不只含抽象類别,且含有具体類别、函數、 及物 件。实际上,框架已涵括了传统類别库(Class Library) 之功能,使得 大家不易区 分框架与類别库之差别了。只能在理論上,区分兩者如下﹕ PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 18 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 应用框架 類别库 ────────────────────── ────────── ───────────── ◎目的﹕让应用程序员衍生出具 ●目的﹕让程序员拿现成類别來诞 体類别,衍生时可修正 生对象,類别并未预留空 類别,才诞生物件。 间给程序员來修正。 ◎应用框架中的類别的函數,常 ●应用程序的函數只能呼叫類别库 呼叫应用程序中的函數。 中的函數,反之不可。 ◎含有類别间之关系,其预设了 ●類别是独立的,并未设定对象间 对象间的互助合作关系。 的沟通方式。 ◎对象常含预设计行为(Default ●对象的行为皆是固定的,无法修 Behavior) ,预设行为可让应 正之。 用程序员修正之。 在实用上,许多人已将它们混为一谈了。 1.2 框架的起源 框架(Framework)的歷史已经有 20 多年了,可追溯到 1980 代 Smalltalk 语 言的 MVC,到了 Apple Macintosh 时代的 MacApp 框架开始大放異彩。逐 步演进到今 天的.Net Framework,其应用范围愈來愈大,已经成为信息服 务系统的共通核心 框架了。20 多年來,框架的基本原理一直都没有改变,其 基本结构也没有太多变化,其基本用法也是老样子,只是应用的场合及范围 不断地扩大,经验不断累积 中。在框架的发展过程中,最具有代表性的是﹕ ● 1980 年代初期 -----Smalltalk-80 的 MVCFramework ● 1980 年代中期 -----Macintosh 计算机的 MacAppFramework ● 1990 年代初期 -----VisualC++ 的 MFCFramework ● 1990 年代中期 -----IBM 的 SanFranciscoFramework ● 2000 年 --------------Microsoft 的.NetFramework PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 1 章 认 識 应用框架 19 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ● 2007 年 --------------Google 的 Android 框架 兹简介如下: 1.2.1 Smalltalk-80 的 MVC 框架 应用框架的起源中,大家最熟悉的是 Smalltalk- 80 语 言 中 的 MVC(Model-View-Controller)框架。其让 Samlltalk 程序 员迅速建立程序的用户 接口(UserInterface)。从 1980 年代的 Smalltalk-80 到 1990 年代 Smalltalk-V ,其 使用者接口皆依循这个著名的框架。典型的 MVC 框架包括三个抽象 類别── Model、View 及 Controller。应用程序从这些抽象類别衍生出具体類 别,并诞生物 件。其对象间的关系如下﹕ 图 1-1 著名的 MVC 框架 model 对象负责管理资料或文件,它可对应到數个 view 对象,每个 view 物 件显示出 model 对象的某一方面﹔每个 view 对象有 1 个相对应的 controller 物 件,其负责解释使用者输入的讯息,如移动滑鼠等等。使用者输入讯息时, controller 依讯息去要求 model 处理档资料,也会要求 view 对象更新画面。一 旦 model 物件中的资料更动了,model 物件会通知各 controller 及 view 对 象,各 view 对象会向 model 取得新资料,然后更新画面。因之典型 MVC 框架是由一群 model、view 及 controller 对象互助合作,做为用户与应用程序 的沟通接口。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 20 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 1.2.2 MacApp 框架 1980 年代中期,已有多种商业性的应用框架上市,其中最流行的是 Apple 公司的 MacApp 框架,其能协助发展 Macintosh 计算机上的应用程 序。这应用程序 包括 3 个部分﹕ ◎ application ──负责启动程序、解释用户的讯息与命令。 ◎ document ──管理与储存应用程序的文件资料。 ◎ view ────显示与输出文件资料。一个程序常含有數个 view,裨 从不同 角度來浏览文件资料。 Macintosh 计算机具有窗口画面。在屏幕画面上,view 依偎在 window 中,且 view 的外围有个 frame。当使用者选取窗口选择表中的项目时,会产 生 command 來要求更新 document 或 view 之内容。因之,由 MacApp 框架所产生的接口, 含有下述对象﹕ ● application 物件 ──负责启动程序、诞生 document 对象,显示窗口选择表,并传递讯息与 命令 等。 ● document 物件 ──负责诞生有关的 view、window 及 frame 等对象。当 document 中的资 料異动时,document 对象会通知 view 对象來取得新资料,并更正窗 口中的内容。 window 对象负责窗口的开关、移动、及通知 frame 对 象來协助改变窗口大 小及卷动等。 ● frame 物件 ──负责将窗口分割为小区域,每区域可摆入一个 view,也负责卷动及改 变窗 之大小。 ● view 物件 ──负责显示资料、记錄滑鼠的位置、以及改变游标的形狀。 ● command 物件 ──当使用者藉滑鼠、选择表及键盘來发出命令时,由 command 对象來转 送给 document 或 view 对象,要求它们采取进一步的行动。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 1 章 认 識 应用框架 21 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 虽然 MacApp 框架中,定义了许多類别來诞生上述的对象,然而应用程序中 通 常 可 直接使用现成的 window 、 frame 及 command 等 類 别 和 物 件。至于application 、 document 及 view 对象,常须加以修正, 才能合乎特定的应用场合。因之,应用程序必须分别由 TDocument 、 TView 及 TApplication 抽象類别衍生出具体子類别,然后才诞生 document、view 及 application 对象。 MacApp 框架成功地结合了屏幕窗口功能,并提供亲切的软件开发环 境,其 应用程序呈现可塑性,能随需求而不断修正。这成功经验,对后來的 应用框架的发展,产生了极大的影响。 1.2.3 VisualC++ 的 MFC 框架 在 1990 年~1993 年之间, BorlandC++ 上市并提供了 OWL 应用框 架,随后 Microsoft C/C++ 及 Visual C++上市,提供了 MFC 应用框架。 OWL 及 MFC 的 目的和功能大致相同──皆为了将 Windows 的 API 界 面函 數 包装 起 來 ,使得 C++ 应用程序员能依循一致的框架,迅速发展 Windows 应用程序。初期的 MFC 包含 兩部分: ◎ 与 Windows 有关的類别──用來包装 Windows 的接口函數。 ◎ 通用性的類别 ──例如 List、Array 、Date 等与常用资料结构有关 的類 别。 后來逐渐增加了更多组件,例如: ● OLE 類别 ──协助应用程序经计算机网路而連结到分散各地的物件。 ● ODBC 類别 ──协助应用程序以统一的 SQL 叙述來存取各式资料库(如 Oracle、Sybase 等)之内容。 MFC 的对象组织与合作方式,類似于 MacApp 的对象群组关系。MFC 含有 CWinApp 、CMainFrame、CView 及 CDocument 等基本類别。应 用程序必须从 这些類别衍生出具体子類别,并诞生对象,互相沟通与合 作。其对象间的关系如 下: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 22 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 图 1-2 MFC 的 Document/View 框架 Windows 将使用者所输入的讯息传给 mainfrm 对象,由 mainfrm 再转达给 view、app 或 document 物件。当 document 对象内的资料有所 異动时,document 会通知 view(程序可含许多 view)对象來取得新资料,以 便更新窗口内容。 1.2.4 IBM 的 SanFrancisco 框架 上述的 MVC、MacApp 及 MFC 皆是担任系统层次的核心任务,如 计算机网 路、分布式资料库的管理工作。到了 1990 年代中期,框架开始 扩展 到商业信息 服务的层面,就是俗称的应用框架(即 Application Framework),又称为商业的領 域框架(即 Business Domain Framework) 。 其 中 最 著 名 的 是 IBM 公 司 的 San Francisco 框架。 IBM 的 SanFrancisco 含有商业服务的企业核心流程,如 ERP 的订单 循环、 会计的应收应付循环等,如下图 1-3 所示。此外也含有像「客户」 及「账户」等 核心的企业对象及对象之间的关系。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 1 章 认 識 应用框架 23 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 图 1-3 IBM 的 San Francisco 组件框架 IBM 的 San Francisco 框架所提供的是 非客制化 的商业核心服务, 让其它 信息服务厂商进行开发客制化的商业应用服务。 1.2.5 微软的 .NetFramework 到 了 2001 年 ,微 软 所 推 出的.Net Framework,其格局更扩大到整个企业 (Enterprise)的分布式框架,甚至包括以 Webservice 为核心的靠企业大型分布 式框架。例如它提供 XML Web Service、MSMQ 异步的讯息服务、 Security 服务 等。在 .Net Framework 裡含有上千个既有的類别,能透 过继承或接口委托方式使用这些類别的功能或服务。 1.2.6 Google 的 Android 框架 于 2007 年 11 月,Google 推出的 Android 应用框架,其适用于「手机+ 网路」 的新市场上。除了它是一个新的应用之外,更珍贵的是其程序代码采 开放策略,让 大家能一窥其全貌,给予软件开发者很好的学习机会。 应用程序 核心企业流程 Business Order Financials Management 共同企业对象 基础服务 JavaVM Unix、NT 等平台 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 24 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 1.3 框架的分层 由于框架介于应用程序与系统程序之间,能大量地重复使用(Reuse) , 并可 不断修正之,因而提升了应用程序之弹性,也能提升系统程序之弹 性。它本身也 可分为兩个层级,如下图: 图 1-4 应用框架之层次 例如,在多平台(Multi-platform) 系统上,弹性是极重要的。在这个层次 裡,框架提供了支持性的服务,通称为支持性框架,让人们不但能使用 操作系统的API 函數,也可以修正之,裨更符合企业的需要。这种支持性的 框架,其观念与 一般应用框架相同,只是它负责系统层次的任务,如计算机 网路、分布式资料库的 管理工作。一般,应用程序员并不直接修正支持性框 架,而是由系统维护人员來 修正之。 在应用层次上,许多企业已着手创造自己的专业框架,整合公司裡的软 件系 统。如果您把应用框架比喻为「食谱」,则不难想象到各領域(Domain) 的 产 业 都 可 能 发 展 出应用框架了。例如,欧洲汽車厂就聯合发展出 AUTOSAR 应用框架, 它们就如同食谱配方,是餐厅赚钱的秘方。因之,在 支持性框架的协助下,许多 专精于某領域的公司,设计出各式各样的应用框 架,像贸易、运输、医療、手机 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 1 章 认 識 应用框架 25 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 等,例如 Android 就是手机+网路的应用框架,让各手机厂商,可经由修正及补 充 來创造出「独家」的应用系统,成为自己公司中的重要资源。 例如,Android 就包含了支持性框架和手机专业应用框架。 1.4 框架的「无用之用」效果 小树的用途少,人们不理睬它、不砍伐它、才有机会长成有用之巨木,此为 「无用」之用﹗老子說过:「人皆知有用之用,而莫知无用之用」,这与框架 观 念是一致的。 數千年前,老子提出了这「有、无」哲理,从无为狀态中创造出有为的 积极 效果。像房子的中间、门、窗皆是空的,才能供人们进出、居住与透透 空气。其 积极效果是﹕日后依新环境的条件而加以调整、充实,创造出多样 化的用途。例 如畚箕的中间是空、虚的,才能装泥土、垃圾等各式各样的东 西。此外,畚箕的 空无,创造了畚箕的重复使用性(Reusability),装完了泥 土,倒掉之后,还可拿來装垃圾等,不断重复使用之,一直到坏掉为止。 不仅上述的树木、房子、畚箕等东西深含虚无之用哲理,在人们的精神 修养 上也常見同样哲理。例如古之贤者常教导年轻人应该「虚」怀若谷,才 能不断虚 心求教,不断吸收新知識,不断充实与成长,成为有用之人。反 之,志得意满的 年轻人,常不愿虚心吸收新知識,常在不知不觉中变为新环 境中的古典人物,为 不断变化的潮流所淘汰。 应用框架中的主角──抽象類别,并非具体的類别,不能用來诞生对 象,看似无用的东西。可是它可衍生出无數个具体子類别,可诞生出无數种 对象來﹗抽象類别中的「抽象(abstract)」函數常是空虚的让抽象類别能虚怀 若谷,让应用程式师不断充实它,其子孙類别就个个精明能干﹗抽象類别 发挥无用之用的效果,应用框架则更进一步地发挥这种效果。人们易于得意 骄傲,不易虚怀若谷。同样 地,易于创造具体類别,而不易创造出抽象 類别。不过,当您懂得藉由眼前的 「无用」來换取长远的「有用」时,创造与使用抽象類别就易如反掌了。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 26 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 1.5 框架与 OS 之关系:常見的迷思 1.5.1 迷思 许多人从空间角度去想象 OS 与应用框架之间的关系。的确,OS(如 Linux 或 Windows)像木板床,应用框架像弹簧床垫,其摆在木版床上。而应 用程序则像睡 在床垫上的人。这个观点是对的(如图 1-4 所示)。 然而,许 多人顺势推論他们之 间的互动关系如下图: 图 1-5 常見的迷思 乍看之下,似乎蛮合理的,其实是个迷思。 请你换个角度,采取另一个观 点,如下图,更容易体会框架的角色和涵意,此新观点如下图 1-6 所示。 回想 一下,您写传统程序时,主控权掌握在程序手中,其决定如何呼叫库存 函數﹔就像棒球比赛的「投手」一样。反之,使用框架时,您的程序则担任 「捕 手」之角色。盼您在使用框架时,能有这种心理准备(Mindset) 。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 1 章 认 識 应用框架 27 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 图 1-6 较合理的观点 1.5.2 藉生活实例來阐述 在 Linux / Windows 的事件驱动观念中,OS 会不断与应用程序沟通, 不断修 正其惯例,裨对外界的事件提供迅速反应与服务。所以 OS 频繁地 主动跟应用程式沟通。如下图: 图 1-7 较合理的观点(相当于上图 1-6) 在日常生活中,也常見这种沟通情形。例如,大饭店(Hotel) 的沟通如下﹕ PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 28 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 图 1-8OS 相当于服务生 再如一般商店的沟通﹕ 图 1-9OS 也相当于店员 当客人问道﹕今天打几折﹖这是个小问 题,店员按惯例(即按公司规定)來 回答﹕8 折。当客人讨价还价而问道﹕可否打 7 折﹖店员请教经理,但经理 并未 给予特别指示,店员就依照惯例回答﹕满 1000 元打 7 折。 图 1-10 店员与经理沟通 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 1 章 认 識 应用框架 29 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 假如,经理有所特别指示,则店员修正惯例如下﹕ 图 1-11 经理的回复 从上述生活实例中,可体会出应用框架之角 色了。与顾客互动的细节几乎都 由店员处理掉了,有必要才会打扰(呼叫)经理,所以经理较轻松了。以此類推, OS 与框架之关系,也相当于框架与应用程序之关系。如下图: 图 1-12 轻松的总裁 与店员的互动细节几乎都由经理人员处理 掉了,有必要才会打扰(呼叫)总 裁,所以总裁就非常轻松了。因此,有了像 Android 的框架(即经理),手机 应用程序(即总裁)就简单许多了。◆ PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 30 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 平台(Platform)的迷思 一谈到平台,许多人就聯想到地基,当然又聯想到漂亮的房子啰!持着这 个 观点是没错,但是坚持这单一观点,常导致多项迷思: 引导我们的眼光聚焦于漂亮的房子。 既然聚焦于房子,就只会以『用』态度去对待平台;就如同一位女生以『用』 的态度去对待男生,男生就不会爱她了。 既然聚焦于房子,就希望买來好地基,缩短建房子的工期;就如同依赖 雀巢的咖啡包、奶精,逐渐地竞相开咖啡厅,造咖啡包的工业就式微了。 为了提供给您另一个观点,笔者把 Android 平台比喻为汉堡: 芝麻:Android 应用程序(房子) 上层面包:Android 框架(平台) 牛肉和 Cheese:框架与硬件之间 的 C 组件 底层面包:硬件组件 每一个观点都没有错,但是多了一些观点,让我们的判断更精准而已。 如 果 您想进一步了解汉堡观点,可先阅讀本书第 11 章。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 2 章 应用框架魅力的泉源:反向沟通 31 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第 2 章 应用框架魅力的泉源: 反向沟通(IoC:InversionControl) 2.1 前言 2.2 认識反向沟通 2.3 主控者是框架,而不是应用程序 2.4 现代应用框架:采取广义 IoC 观念 2.5 框架的重要功能:提供预设行为 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 32 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 2.1 前言 上章 裡 ,您 已知道应用框架之目的,也了解它在软件设计上之角色。本 章裡,将专注于框架之主角──抽象類别上,說明抽象類别之特性,分析「抽 象」与 「具体」類别之间的双向沟通方法。应用框架中最令人着迷之处是: 框架裡的函數,能呼叫应用程序的函數。 ﹌﹌﹌﹌﹋﹌﹌﹋﹌﹌﹌﹌﹌﹋﹌﹌﹋﹋ 这是框架与一般類别库(或链接库)的极重要区别。使用一般链接库 时,程式中的函數呼叫了现成的库存函數,但库存函數不能反过來,呼 叫您所写的函數。由于库存函數设计在先,而您写程序在后﹔所以,您的函 數呼叫库存函數, 这种晚辈呼叫前辈的传统沟通情形,是您已非常熟悉的 了。 应用框架除了能进行传统沟通外,还提供新潮方法:前辈呼叫晚辈。虽 然前 辈(应用框架)诞生时,晚辈(应用程序)尚未诞生﹔但是前辈有时候 可预知晚辈中的函數,就可呼叫它。这种功能,具有下述效果: ☆ 框架能事先定义许多「预设」(Default)函數。预设(default) 函數就 是依 惯例而设定之函 數。惯例是自动化科技的基本观念,也是 应用框架的重要机制。例如,搭计程車时,您只要告诉计程車司 机:「到士林夜市」,司机会依照其经验习惯而选取路线,让您舒 适抵达夜市。更重要 的是,您可特别指示司机,他会按照您(即应 用程序)的意思而「修正」其惯例。 ☆ 应用程序员的主要工作是:设计函數供框架來呼叫。这些 函數可修正或取代框架中的函數。 ☆ 如 果 程序中的函數已修正或取代预设函數,框架就呼叫程 序中的函數﹔反之则呼叫预设函數。 这些效果正满足当令流行的「事件驱动」(Event-Driven)软件的需要,如下 图 2-1 所示。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 2 章 应用框架魅力的泉源:反向沟通 33 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 使用者 事件 1 事件 2 事件 3 OS(Linux/Windows) 讯息 讯息 讯息(呼叫) 应用框架 预设 f1() abstract f2() 预设 f3() 预设 f4() 讯息 讯息 讯息 讯息 应用程序 f1() f2() f4() 图 2-1 应用框架与事件驱动软件 这是在 Linux 或 Windows 等操作系统下,应用框架的典型双向沟通情形, 兹 将上述 4 种呼叫情形說明如下: 1. 框架中预设了 f1(),程序中也定义了 f1()。此 时优先呼叫晚辈的 f1() 函數。 2. 框架「虚」设了 f2(),亦 即 f2()是个抽象(abstract)函 數 。此 时您务必 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 34 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 定义 f2()來充实之,并供 Linux/Windows 或其它函數呼叫。例如 f3() 呼叫 f2()。 3. 框架预设了 f3(),程序并未定义 f3()。此时呼叫预设的 f3()函數。 4. 框架预设了 f4(),您也定义了 f4()。此时优先呼叫 f4()函數,而 f4() 可呼叫前辈(预设)的f4()函數。 从上所述,您可看出重要现象:框架与程序之间,主控权是在框架手 上,您写的函數皆供框架呼叫 ﹋﹌﹌﹌﹌﹋﹌﹌﹌﹌﹋﹌﹌﹌﹌﹋﹌﹌﹌﹌﹋﹌﹌﹌﹌﹋﹌﹌﹌﹌﹌ 回想一下,您写传统程序时,主控权掌握在程序手中,其决定如何呼叫 库存 函數﹔就像棒球比赛的「投手」一样。反之,使用框架时,您的程序则 担任「捕 手」之角色。盼您在使用框架时,能有这种心理准备(Mindset) 。 2.2 认識反向沟通 ---- 又称为反向控制(IoC) 通常框架都是设计在先,而应用程序则设计在后,这种前辈拥有主导 权,进而「控制」后辈之情形,就通称为「反向控制」。顾名思义, IoC(Inversion ofControl)就是「反向控制」之意思。而它是相对 于「正向控制」一 词,所以在本节 裡,将先介绍「正向控制(沟通)」之 涵 意,就能迅速理解「反向 沟通」之 意 义 了 。 IoC 观念和机制源自于 OO 语言(如 C++、Java 等)的類别继承 体系,例如 Java 语言中,父 類别(Superclass)的函數可以主动呼叫子類别(Subclass) 之函數,这就是最传统的 IoC 机制,称为「继承体系 IoC」。后 來,人们常 将许多相关的父類别 聚集起來成为框架,逐渐地,延伸为:应用框架主动呼叫 应用程序之情形,就称 为 IoC 。或者說:会主动呼叫应用程序之框架,就称为 IoC 框架,例如 Android、Spring 等等。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 2 章 应用框架魅力的泉源:反向沟通 35 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 2.2.1 正向沟通 传统的链接库(Function Library)已含有许多现成的函數(前辈),您的程序 (晚辈)可呼叫之。例如, publicmyFunction(){ intx=abs(y); …… } abs()是您已很熟悉的库存函數,它诞生在先,是前辈;您的程序(晚 辈)诞 生在后,是晚辈。这是传统呼叫法:晚辈呼叫前辈。一般類别库(Class Library)含 有现 成 的 類 别, 这些類别含有函數,可供新類别的函數來呼叫 之。例如,先有个 Person 父類别如下: publicclassPerson{ privateStringname; publicvoidSetName(Stringna){ name=na; } publicvoidDisplay() { System.out.println("Name:"+name); } } 接着,您可以写个子類别 Customer 去继承它,并且呼叫它的函數,如下: publicclassCustomerextendsPerson{ publicvoidInit() { super.SetName(“Tom”); } publicvoidShow(){ super.Display(); } } 上述的 Init()呼叫了晚辈 SetName()函數。或者,写个 JMain 類别: publicclassJMain{ privatep; publicvoidInit(){ p=newCustomer(); p.SetName(“Tom”); } publicvoidShow(){ p.Display(); } } 这也是晚辈呼叫前辈的情形。由于大家已习惯了这种晚辈呼叫前辈的用 法, 就通称为「 正 向 」(Forward) 呼叫法。由于晚辈拥有主控权,所以这种机制 又称为 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 36 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 「正向控制」。再來看看本书的主角:Android 框架,以下就是 Android 的应用 程式码: //Android 程序 publicclassMyActivityextendsActivity { @Override publicvoidonCreate(Bundleicicle) {super.onCreate(icicle); setContentView(R.layout.main); } 上 述 晚辈(即 MyActivity)的 onCreate()呼叫了晚辈(即 Activity)的 onCreate()和 setContentView()函數。而 Activity 就是 Android 框架裡的重要抽象類别。 2.2.2 反向沟通 当子類别继承父類别时,父類别之函數可以呼叫子類别之函數。虽然父類 别 (前辈)诞生时,子類别(晚辈)常常尚未诞生﹔但是前辈有时候可预知晚辈中 的函數,就可呼叫它。框架裡的抽象類别就是扮演父類别的角色,只是含有 一些阳春 型的類别,其提供很通用,但不完整的函數,是设计师刻意留给应 用程序的子類 别來补充的。一旦补充完成,框架裡的父類别的函數就可以 「反向呼叫」子類别 裡的函數了。 2.2.2.1 以一般 Java 程序为例 例如:有了一个绘图的 Shape 父類别: //Shape.java package_framework; publicclassShape{ publicvoidPaint(){this.Draw();} publicabstractvoidDraw(); } 设计者预期子類别将会定义一个 Draw()函數,于是让 Paint()呼叫子類 别的 Draw()函數。于是子類别(晚辈)提供 Draw()给父類别(前辈)來呼叫之,如下: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 2 章 应用框架魅力的泉源:反向沟通 37 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ //Circle.java package_objects; importjava.awt.Color; importjava.awt.Graphics; import_framework.*; publicclassCircleextendsShape{ privateGraphicsm_gr; privateintx,y,radius; publicCircle(Graphicsgr) {m_gr=gr; } publicvoidSetValue(intx0,inty0,intrad){ x=x0; y=y0; radius=rad; } public void Draw(){ //画圆 m_gr.setColor(Color.BLACK); m_gr.drawOval(x-radius,y-radius,2*radius,2*radius); }} 接者,写个 JMain 類别: //JMain.java importjava.awt.*; importjavax.swing.*; import_framework.Shape; import_objects.*; classJPextendsJPanel{ publicvoidpaintComponent(Graphicsgr){ super.paintComponents(gr); Circlecir=newCircle(gr); cir.SetValue(160,100,45); Shapesp=cir; sp.Paint(); }} publicclassJMainextendsJFrame{ publicJMain(){ setTitle(""); setSize(400,300);} publicstaticvoidmain(String[]args) {JMainfrm=newJMain(); JPpanel=newJP(); frm.add(panel); frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frm.setVisible(true); PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 38 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ }} 此程序执行到 JP 類别的 sp.Paint()指令时,就呼叫到 Shape 父類别的 Paint()函 數,接着就呼叫到 Circle 子類别裡的 Draw()函數了。这种前辈呼叫 晚辈的用法, 就通称为「反向」(Inversion) 呼叫法。由于前辈拥有主控 权,所以这种机制又称 为「反向控制」(InversionofControl)。 2.2.2.2 以 Android 程序为例 例如想 在 Android 上画出一个长方形,可写程序如下: //Android 程序 publicclassMyViewextendsView{ privatePaintpaint; publicMyView(Contextcontext){ super(context); privatePaint paint=newPaint(); } publicvoidReDraw(){ this.invalidate(); } @Override protectedvoidonDraw(Canvascanvas){// 画长方形 paint.setAntiAlias(true); paint.setColor(Color.YELLOW); canvas.clipRect(30,30,100,100); }} 程序执行到 ReDraw() 函 數 时 , 就 正 向 呼叫到 Android 框架裡的 invalidate()函 數了。接着,Android 框架会反过來呼叫 MyView 子類别的 onDraw()函數。这就 是「反向沟通」了。如果你没有定义 onDraw()函數的 话,会执行 View 父類别预 设的 onDraw()函數,而依据框架预设之惯例而 行了。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 2 章 应用框架魅力的泉源:反向沟通 39 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 2.3 主控者是框架,而不是应用程序 前面說过,传统的链接库及類别库只提供正向沟通,而应用框架则提供 正向 和反向兼具的双向沟通。本节针对应用框架的双向沟通,做进一步的阐 述双向沟通机制让框架成为主控者,而应用程序只是配角而已。首先看个例 子,假设已经设计了 Person 及 Customer 兩類别并存于应用框架中,如下图 所示: Person Customer (框架) 图 2-2 一个简单的框架 兹以 Java 程序表达如下: //Person.java package_abstract_classes; publicabstractclassPerson{ protectedStringname; publicvoidsetName(Stringna){name=na;} publicabstractvoiddisplay(); } //Customer.java package_abstract_classes; publicclassCustomerextendsPerson{ publicvoiddisplay() {System.out.println("Customer:"+super.name);} } PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn VIP 40 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 现在,基于这个框架而衍生出 VIP 子類别,如下图: Person (框架) (应用程序) 图 2-3 衍生出应用程序的類别 其 Java 程序代码如下: //VIP.java package_concrete_classes; import_abstract_classes.*; publicclassVIPextendsCustomer{ privateStringtel; publicVIP(Stringna,Stringt){ super.setName(na); tel=t; } publicvoiddisplay() {super.display(); System.out.println("TEL:"+tel); }} Customer PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 2 章 应用框架魅力的泉源:反向沟通 41 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 建 构 式VIP() 呼叫父類别的 setName() 函 數 , 且 display() 呼叫Customer::display()函數,这 兩者皆为正向沟 通。亦即,程序中的函數呼叫框架中 的函數。现在继续增添反向沟通机制。 例如,于框架中增加了 Product 類别,此 时,框架共含三个類别。基于这 框架,您可衍生出子類别如下图所示: Product pc Person Customer (框架) TV VIP (应用程序) 图 2-4 框架掌握更多主控权 其 Java 程序代码如下: //Product.java package_abstract_classes; publicabstractclassProduct{ protectedintpno; protectedCustomercust; publicProduct(intno){ pno=no; } publicvoidsoldTo(Customercobj){ cust=cobj; } publicvoidinquire(){ this.print(); System.out.println("soldto..."); cust.display(); } PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 42 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ public abstract void print(); } // TV.java package_concrete_classes; import_abstract_classes.*; publicclassTVextendsProduct{ privatedoubleprice; publicTV(intno,doublepr){ super(no); price=pr; } publicvoidprint() {System.out.println("TVNo:"+pno); System.out.println("Price:"+price); }} 其反向沟通机制如下图: public class Product { .... public void inquire() { public class Person { .... } (反向沟通) } .... this.print(); cust.display(); (反向沟通) public class Customer extendsPerson{ .... } } public class TV extends Product { .... public void print() .... }} public class VIP extendsCustomer{ .... publicvoid display(){ .... 继续看个主函數,会更清晰地理解框架的主控地位,如下程序代码: //JMain.java import_abstract_classes.*; PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 2 章 应用框架魅力的泉源:反向沟通 43 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ import_concrete_classes.*; publicclassJMain{ publicstaticvoidmain(String[]args) {TVt=newTV(1100,1800.5); VIPvp=newVIP("Peter","666-8899"); t.soldTo(vp); t.inquire(); }} Product 父類别设计在先,然后才衍生 TV 子類别,而且常由不同人所设 计。 那么,何以 Product 類别的 inquire() 敢大胆地呼叫 TV 類别的 print() 函數呢﹖万 一 TV 類别并无 print()时,怎么办呢﹖答案很简单: ☆ TV 類别必须定义 print()函數,才能成为具体類 别。 因为 Product 裡的 print()是抽象函數,内容 从缺: publicabstractvoidprint(); 其中的 abstract 字眼,指示子類别必须补充之,才能成为具体類别。 ☆ TV 類别成为具体類别,才能诞生对象。 ☆ 有了对象才能呼叫 inquire()函數, ☆既然TV 類别已覆写 print()函數,inquire() 可大胆地呼叫 之。于是,必须替TV 類别添增 print()函數如下: publicvoid print() {System.out.println("TVNo:"+pno); System.out.println("Price:"+price); } 执行时,就产生反向呼叫的现象了。 此外,Product 類别的 inquire() 呼叫 VIP 類别的 display()函數。Product 類别 与 VIP 類别 并非同一个類别体系。此时, VIP 類别必须是具体類别才能诞生对象。 cust 变數必须參考到刚诞生的对象。 由 cust 所參考之物件來执行其 display()函數。 inquire()就透过 cust 而成功地呼叫到 VIP 類别的 display()了。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 44 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 这过程有个先决条件──VIP 類别必须覆写 display()函數才行。否则将会 呼叫 到 Customer 類别预设的 display(),而不是呼叫到 VIP 類别的 display() 函數。也许,您还会问一个问题:何不将 cust 变數改宣告为 VIP 型态之參 考呢﹖答案是: 别忘了,抽象類别通常设计在先,而具体類别产生在后。因 之,设计 Product 類 别时,VIP 類别尚未诞生呢﹗ 这程序展现了应用框 架的重要现象: ☉程序执行时,主控权在框架手上。 虽然 main()函數仍为程序的启动者,但主要的处理过程皆摆在 Product 類别 内。例如, ● soldTo()负责搭配产品与顾客之关系。 ● inquire() 负责呼叫 TV 的 print() 输出产品资料,并呼叫 VIP 的 display() 输出顾客资料。 ☉程序裡的類别,其成员函數,主要是供框架呼叫之。 例如,TV 類别的 print()供 inquire()呼叫之,而 VIP 類别的 display()供 inquire() 呼叫之。 ☉由于框架掌握主控权,复杂的指令皆摆在框架中,大幅简化应用程 序。 因之,优良的应用框架常让程序员如虎添翼。 ☉框架裡的 inquire() 进行反向沟通,它呼叫子類别的 print() 函 數。 这是同体系内的反向呼叫。 ☉框架裡的 inquire() 反向呼叫 VIP 的 display() 函數。 因 Product 与 VIP 分属不同的類别体系。这是跨越体系的反向沟通。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn Integer 第 2 章 应用框架魅力的泉源:反向沟通 45 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 2.4 现代应用框架:采取广义 IoC 观念 上 一 节 所 提 的 反向沟通都是仰赖 Java 的 類 别继承机制來达 成,例如把Product 類别纳入框架中,应用程序员就能继承 Product 而衍生 出 TV 子類别。然后在执行时(Run-time),整个程序的主控权就掌握在 Product 的 inquire()函 數 手 中,这是仰赖類别继承的传统「反向控制」 (IoC)。由于數十年來,愈來愈多的框架上市了,几乎所有的框架都擅用 IoC;于是,IoC 一词之涵义逐渐地扩大为: 框架拥有主控权,它主动呼叫应用程序的情形,就通称为 IoC。 如此,IoC 就不限于上述的類别继承情形了。只要框架主动來呼叫应用程 序 裡的類别,就是一种 IoC 了。例如下图: Factory Document Initialize() { doc=newDocument(); doc.Setter( new Integer() ); ………… } (框架) Document (应用程序) IDisplaypd; voidSetter(IDisplayd){dp=d;} void Display(){dp.Display(); } IDisplay 图 2-5 不 是透 过 類 别继承的框架主动呼叫 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 46 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 兹以 Java 程序來实现上图结构,其步骤如下: Step-1: 建立一个 Java 项目,并定义 接口 在此项目裡,建立三个套件如 下: 然后在_framework 套件裡定义一个接口,其 Java 程序代码如下: //IDisplay.java package_framework; publicinterfaceIDisplay{ publicvoidDisplay(); } 并且在_framework 套件裡定义 Factory 類别,其 Java 程序代码如下: //Factory.java package_framework; import_objects.*; import_objects.Integer; publicclassFactory{ privateDocumentdoc; publicDocumentInitialize() {doc=newDocument(); doc.Setter(newInteger()); returndoc; }} Step-2: 定义应用对象 在_objects 套件裡定义兩个類别:Document 和 Integer,其 Java 程序代码如 下: //Document.java package_objects; import_framework.*; PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 2 章 应用框架魅力的泉源:反向沟通 47 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ publicclassDocument {IDisplaydp; publicvoidSetter(IDisplayd){dp=d; } publicvoidDisplay(){dp.Display();} } //Integer.java package_objects; import_framework.*; publicclassInteger implementsIDisplay {intvalue; publicInteger(){ value=100; } publicvoidDisplay() { System.out.println("Value="+String.valueOf(value)); }} Step-3: 设计 JMain 主应用程序 在(default package)套件裡定义一个類别:JMain,其 Java 程序代码如下:Java 程序代码如下: //JMain.java import_objects.*; import_framework.*; publicclassJMain{ publicstaticvoidmain(String[]args) {Factoryfa=newFactory(); Documentdoc=fa.Initialize(); doc.Display(); }} Step-4: 执行上述程序 当您仔细观察上述程序范例时,会发现框架也掌握了对 象的生殺大权,负责 诞生应用程序之对象。请看 Factory 類别的内容: // 框架 publicclass Factory { privateDocumentdoc; publicDocumentInitialize(){ doc=newDocument(); PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 48 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ doc.Setter( new Integer()); returndoc; }} 其掌握了应用程序对象之生命周期(Lifecycle),其含有对象參考 (Reference) 參考到应用程序之对象,也就是它「包含」(Contain)了应用程序之对象。所 以, 这种框架又通称为 Container。此外,在建立对象之刻,也呼叫应用对象 的 Setter() 函 數 ,建立出 Document 与 Integer 兩 个对象之相依关系 (Depencency)。换句话說,应用对象之间的相依关系之建立是掌握在框架手 中,由框架主动呼叫应用程序而 建立的,这让应用程序不必诞生其它应用对 象,也不必费心管理应用对象之间的 相依性。而是由框架替应用程序『注 入』相依关系,免除了应用对象之间的相依 关系,如同:注射流感疫苗, 而免除了流感。这通称为相依性注射(DependencyInjection)。 2.5 框架的重要功能:提供预设行为 2.5.1 预设行为之意义 框架裡的函數内容,通常扮演「预设函數」的角色,表达了惯例之行 为。惯 例是自动化科技的基本观念,也是软件设计的重要观念。拿汽車自动 排挡做例子 吧﹗自动排挡的优点是:汽車会「自动地」依照速度而换挡,亦 即会依惯例來维 持汽 車的平稳。这还不够,若由司机驾驶更佳﹗例如您 只要告诉计程車司机: 「到士林夜市」,司机会依照其经验习惯而选取路线,让您舒适抵达夜市。 更重 要的是,您可特别指示司机,他会按照您的意思而「修正」其惯例。因 之惯例的 重要特色为: ● 让使用者更加轻松愉快。 例 如上述汽車的三个层次是: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 2 章 应用框架魅力的泉源:反向沟通 49 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 汽車 司机 乘客 因为汽車会自动依惯例换挡,司机就轻松多了。也因为司机会依惯例 选 择理想的路线,乘客不必操心。 ● 惯例是可修正的。 惯例只适合一般狀况,若遇特殊狀况发生,应立即修正 之。例如波音 747 客机会依照惯例起降,但遭遇特殊狀况(如碰到一大群鸽子),飞行员会立 即修正之。这飞行员的判断凌驾于惯例之上,达到修正之目的。在计算机 软件 上,也具有三个层次: 计算机硬件 操作系统 应用程序 操作系统包含了各式各样的惯 例函數,自动化地指挥硬件,其降低 了应用程序之负担。Linux/Windows 等操作系统已有所改进了。在事 件驱动观念 中,操作系统会不断与应用程序沟通,不断修正其惯例,裨 对外界的事件提供迅速反应与服务。如下图: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 50 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 计算机硬 件 操作系统 应用程序 抽象類别 具体類别 抽象類别的预设函數扮演「备胎」角色,当子類别并未覆写(Override)该函數 时,就会使用备胎。一旦将抽象類别移入框架中,框架就提供预设行为了。 2.5.2 以 Java 程序阐述预设行为 在 Java 裡,预设行为通常表现达于父類别的函數裡,让子類别自由决定 要不 要覆写(Override)它,如果覆写它,就会执 行子類别的 函數;反之。 如果不覆写它,就会执行父類别的预先所写的函數。请看下述的预设函數之 范例: //Employee.java package_objects; publicabstractclassEmployee{ publicabstractvoidSetFee(floatbasic_fee,floatdisc); publicabstractfloatGetTotal(); publicabstractvoiddisplay(); } //SalesPerson.java package_objects; publicabstractclassSalesPersonextendsEmployee{ PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 2 章 应用框架魅力的泉源:反向沟通 51 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ protectedStringname,sex; protectedfloatBasicFee,Discount; publicSalesPerson(Stringna,Stringsx){name=na; sex=sx;} publicvoidSetFee(floatbasic_fee,float disc){BasicFee=basic_fee; Discount=disc; } publicvoiddisplay(){ System.out.println(name+", Fee:"+this.GetTotal()); }} //SalesSecretary.java package_objects; publicclassSalesSecretaryextendsSalesPerson{ publicSalesSecretary(Stringna,Stringsx) {super(na,sx); } publicfloatGetTotal() {returnBasicFee*Discount-100; } } //JMain.java import_objects.*; publicclassJMain{ publicstaticvoidmain(String[]args){ Employeelinda=newSalesSecretary("LindaFan","Female"); linda.SetFee(2500f,0.7f); linda.display(); }} 请你仔细看看此程序的执行过程,是很微妙而有趣的。此程序执行时,先 执 行 JMain 類别的 main()函數,执行到指令: linda.display(); 就转而执行 SalesPerson 類别预设的 display()函數: publicvoiddisplay(){ System.out.println(name+", Fee:"+this.GetTotal()); } 执行到指令:this.GetTotal(); 就转而执行 SalesSecretary 類别的 GetTotal()函數: publicfloatGetTotal(){ returnBasicFee*Discount – 100; } PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn SalesPerson display(){ this.GetTotal() } (反向呼 52 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 这程序显示了「抽象類别 + 预设函數」的美妙组合,如下图所示: JMain main() { 叫) linda.display() } SalesSecretary GetTotal() { ……. } 虽然 main()函數仍为程序的启动者,但主要的处理过程是在 SalesPerson 的 display()函數内。是它决定呼叫 GetTotal()的。子類别 SalesSecretary 扮演配 角,其 GetTotal()只是供 SalesPerson 的 display()函數來呼叫之。前面也提供, 因为抽象類 别掌握主控权,复杂的指令皆摆在抽象類别中,因而大幅简化了 具体類别开发者的负担。◆ PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 53 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第二篇 无之(抽象)以为用 ~~老 子 .道 德 经:有之以为利,无之以为用~~ 畚箕必须先挖空(无之)才能拿來装东西(有之 )。所以先无之而后始能有之。 抽象(Abstraction)是 达 到 无 之 的 一 种 手 段 或技 术。抽象出父類别是软件业者实践无之的惯用 手艺。而衍生则是软件业者实践有之的惯用手 艺。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 54 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第 3 章 如何打造 应用框架 3.1 基础手艺:抽象(无之)与衍生(有之) 3.2 打造框架:细腻的抽象步骤 3.2.1 基本步骤 3.2.2 细腻的手艺(一):比较资料成员 3.2.3 细腻的手艺(二):比较函數成员 3.2.4 细腻的手艺(三):将抽象類别转为接口 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 55 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 请注意: ※ 如果你只对如何「使用」Android 框架有兴趣的话,可以跳 过本 章,直接进入第 4 章。 ※ 基于前兩章的概念,你已经对应用框架有足够的基础可好好使用 Android 应用框架了。 ※ 如果你想探讨如何构思及打造应用架构,本章可带你入门,然而 应用框架的打造,艺术含量颇高,工法与心法兼俱,尤须经验 的累积,始能精益求精。本章提供给你一个美好的起点。 3.1 基础技艺:抽象(无之)与衍生(有之) 前面兩章介绍了如何以 Java 表示抽象類别,以及如何建立類别继 承体系等;其都偏向技巧,着重于表达之形式及其正确性。基于前面兩章 所建立的基 础,本章将进入构思与设计层次,說明如何发挥人類天赋的 抽象能力,除了上 述的正确性之外,更追求设计的美感,设计更优雅的 接口、整合出更具整体和 谐之应用框架。 「抽象」(Abstract)一词常令人觉得那是「难以体会」的事。在软件设计 上, 如果您把「 抽象」定义为「 抽 出 共 同 之 现 象 」, 就 是 件 轻 松 愉快的事 了。例如, 观察兩个相似的類别,并分辨其相同与相異点,然后把相同 点抽離出來,构成 父類别,就是抽象類别了。就广义上而言,凡是经由下 述过程: Step1. 观察几个相似的類别。 Step2. 分辨它们的異同点。 Step3. 把它们的相同点抽離出 來。 而导出的父類别皆称为抽象類别。 ◎ 以正方形为例 兹拿个简单例子來說吧﹗有三个方形,分别为直角、圆角及缺角,如下: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 56 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 首先分辨它们的異同点,然后将其共同部分抽離出來,如下: 我们称这过程为「抽象」(Abstraction) 。并称此图形为「抽象图」,其只含 共同部分,而相異部分从缺。原有的直角及圆角方形,为完整图形,称为「 具 体 图 」 。 一 旦 有 了 抽象图,就可重复使用(Reuse) 它來衍生出各种具体 图,且事半 功倍﹗例如: ●用途 1 ── 衍生直角方形。 拷贝一份抽象图,在图之四角分别加上┌、┘、└及┐,就 成为直角方形了,如下: ●用途 2 ── 衍生圆角方形。 拷贝一份抽象图,在图之四角分别加上╭、╰、╯及╮,就成 为圆角方形了,如下: ●用途 3 ── 衍生球角方形。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 57 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 拷贝一份抽象图,在图之四角各加上●就成为: 这个简单例子,說明了无之以为用,有之以为利的设计哲理。 ◎ 以火锅店为例 例如,有一家 火锅店,它的客人有些要吃石头火锅,有些要吃沙锅鱼头, 也有些要吃韩国碳烤等。所以客人的需求是多样化的,如下图所示: 客人 1 吃: 石 头火锅 客人 2 吃: 沙 锅鱼头 客人 n 吃: 韩国碳烤 图 3-1 火锅店的桌子 火锅店的基本设计程序是: Step1---- 厘清客制化与非客制化之界线 因为这些桌子,除了锅子部份不一样之外,其余是相同的,界线清楚了。 Step2---- 分離客制化与非客制化部分 将锅子与桌子分離开來,也就是把变異部份抽離出來(在桌面上挖一个 洞)之后,剩下的部份就相当一致了。如下图 3-2 和图 3-3 所示: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 58 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 一致的接口 抽掉(分離) 石头火锅 沙锅鱼头 韩国碳烤 图 3-2 将客制化部份分離出來 把变 異部份抽離出來(在桌面上挖一个洞)之后,剩下的部份就相当一致 了,设计师只需要设计一款桌子(即框架)就可以了,火锅店可视空间及客人數 而决定需订制几张桌子(即大量订购框架)。因为不需要为石头火锅、沙锅 鱼 头、韩国碳烤等各设计其专用的桌子,所以能大量生产桌子(即框架的无限 量 产)。至于锅子部份,因为石头火锅、沙锅鱼头、韩国碳烤等各有所不同,所 以 必须个别设计(即客制化),如下图 3-3 所示: 框架(抽象類别) 多样化的组件(子類别) 图 3-3 无之,始能得框架 从上述简单的生活例子中,您能 体会出框架的基础手艺:细心厘清客制化 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 59 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 与非客制化的界线,将其「分」(即无之)離开來,配合客人多样性的需求而量身 定做客制化部分,然后将兩者组「合」(即有之)成为客人所喜爱的产品或服务。 换句话說,一旦有了框架和界面之后,当客人上门了,桌子与锅子就能一拍即 合,如下图所示: 框架(抽象類别) 锅子(子類别) 泡菜锅餐桌 锅子(子類别) 砂锅鱼头餐桌 图 3-4 有之,始能服务客人而获利 火锅桌子组件是「实」的,在桌面上挖一个洞之后,得出一个接口,此接 口 塑造出一个「虚」的空间,此虚的空间可用來容纳多样性的小组件 ----- 石头火锅、韩国碳烤等,而这些小组件也是「实」的。就像老子在數千年前 已经說过, 像房子的中间、门、窗皆是虚 的空间的,才能供人们进出、 居住与透透空气。 其积极效果是:日后依新环境的条件而加以调整、充实,创造出多样化的 用 途。例如畚箕的中间是空、虚的,才能装泥土、垃圾等各式各样的东 西。此外,畚箕的空无,创造了畚箕的重复使用性(Reusability) ,装完 了泥土,倒掉之后,还可拿來装垃圾等,不断重复使用之,一直到坏掉为 止。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 60 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 3.2 打造框架:细腻的抽象步骤 3.2.1 基本步骤 上一节的日常生活实例中,說明了兩个重要动作: ☆抽象── 从相似的事物中,抽離出其共同点,得到了抽象结构。 ☆衍生── 以抽象结构为基础,添加些功能,成为具体事物或系统。 同样地,在软件方面,也常做上述动作: ★抽象── 在同領域的程序中,常含有许多類别,这些類别有其共同 点。程式师将類别之共同结构抽離出來,称为抽象類别 (AbstractClass)。 其抽象之步骤是: Step1. 观察几个相似的類别。 Step2. 分辨它们的異同点。 Step3. 把它们的相同点抽離出來。 ★衍生── 基于通用结构裡的抽象類别,加添些特殊功能,成为具体類 别, 再诞生对象。 所以「抽象類别」存在之目的,是要衍生子類别,而不是由它本身來诞生对象。 由于抽象類别本身不诞生对象,所以有些函數并不完整。反之,如果類别内 之 函 數 ,皆是完整的,而且要用來诞生对象,就称它为具体類别 (Concrete Class)。所谓不完整,就是函數的内容从缺,例如: publicabstractclass Person { //....... publicabstractvoidDisplay(); } 这 Display()函數内的指令从缺,等待子類别來补充,如下: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 61 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ //EX03_01 //Person.java package_objects; publicabstractclassPerson{ protectedStringname; publicvoidSetName(Stringna) { name=na; } publicabstractvoidDisplay(); } //Employee.java package_objects; publicclassEmployee extendsPerson{ publicvoidSetName(Stringna) {super.SetName(na); } publicvoidDisplay() {System.out.println("Employee:"+name); } } //JMain.java import_objects.*; publicclassJMain{ publicstaticvoidmain(String[]args) {Personp=newEmployee(); p.SetName("PeterChen"); p.Display(); } } 这 Employee 是个子類别, 已经将 Display() 函數充实完整,可用來诞生 对象 了,此时 Employee 为具体類别。 那么,什么时候会跑出像 Person::Display()这种抽象的(即内容从缺 的)函 數呢﹖答案是:在上述第 3 步骤中,抽離出共同点时,因为 Display() 函數之内 容不相同,只抽離出函數名称而已。例如,有兩个類别 如下: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 62 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ public class Employee { privateStringname; 相同 privateintsalary; publicvoidSetName(Stringna){ 相同 name = na; } publicvoidSetSalary(ints){ salary=sa } publicvoidDisplay() 相同 { System.out.println(“Emp: ” + name+“Salary: ” +salary); public class Customer { privateStringname; publicvoidSetName(String na){ name=na } publicvoidDisplay() { System.out.println( “Cust: ” +name) } } } } 首先把相同的资料成员抽離出來,如下: publicclassPerson{ privateStringname; } 接着,把相同的函數成员抽離出來,如下: publicclassPerson{ privateStringname; publicvoidSetName(Stringna){ name=na;} } 最后,将名称相同,但内容不同之函數抽離出來,成为抽象函數如下: publicabstractclassPerson{ privateStringname; publicvoidSetName(Stringna){ name=na;} publicabstractvoidDisplay(); } 由于只抽出 Display() 的名称,而内容从缺,这就是抽象函數。于是,Person 就 成为抽象類别了。从上述实例之中,可归纳出「抽象」的三个分节动作: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 63 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ☆ 分辨──明察秋毫,把稳定与善变部份区分出來。 ☆ 封藏 ──把差異部份的指令封藏于子類别中。 ☆ 抽象 ──把類别的共同点抽象出來,成为抽象類别。 在 Java 程序上,抽象類别必须与具体類别合作,才能诞生对象來 提供服务;抽象類别跟具体類别有密切的互动,因而必须熟悉如下兩项重 要的手艺, 才能落实好的抽象过程: ●产生抽象類别。 ● 加入预设(Default)指令,提高弹性,仍保持共通 性。 现在,就准备产生抽象類别。请从一个简单 Java 程序介绍 起吧! //EX03_02 //Employee.java package_objects; publicclassEmployee {privateStringname; privateStringsex; staticclassFee{ publicstaticfloat BasicFee; publicstaticfloat Discount; publicstaticfloatGetTotal(){ return BasicFee * Discount;} } publicEmployee(Stringna,Stringsx){ name=na; sex=sx; } publicvoidSetFee(floatbasic_fee,floatdisc) {Fee.BasicFee =basic_fee; Fee.Discount =disc; } publicvoidDisplay(){System.out.println(name+"'sfee:"+Fee.GetTotal());} } // Customer.java package_objects; publicclassCustomer {privateStringname; privateStringsex; staticclassFee{ publicstaticfloat AnnualFee; PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 64 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ publicstaticfloat Discount; publicstaticfloatGetTotal() {return AnnualFee *32.25f*Discount;} } publicCustomer(Stringna,Stringsx) { name=na; sex=sx; } publicvoidSetFee(floatannual_fee,floatdisc) {Fee.AnnualFee =annual_fee/32.25f; //ConverttoUS$toNT$ Fee.Discount =disc; } publicvoidDisplay() {System.out.println(name+"'sfee:"+ Fee.GetTotal());} } // JMain.java import_objects.*; publicclassJMain{ publicstaticvoidmain(String[]args){ Employeeemp=newEmployee("Tom","M"); Customercust=newCustomer("Lily","F"); emp.SetFee(1000f,0.9f); cust.SetFee(500f,0.75f); emp.Display(); cust.Display(); }} 此程序输出: Tom'sfee:900.0 Amy'sfee:375.0 当您看到这兩个類别---- Employee 与 Customer 时,就像看到火锅店裡 的兩 张餐桌,有些部份是一致的,也有些差異部份。類别裡包含兩项重 要的成份: 资料成员和函數成员。于是先对资料成员比较一翻吧! PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 65 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 3.2.2 细腻的手艺(一):比较资料成员 兹比较兩類别的资料成员: public class Employee { privateStringname; private String sex; ststic class Fee { publicstaticfloatBasicFee; public static float Discount; public static float GetTotal() { return BasicFee * Discount } 相同 } ....... } 相異 public class Customer { private String name; private String sex; static class Fee { public static float AnnualFee; public static float Discount; public static float GetTotal() { return BasicFee * 32.25 * Discount } } ....... } 接着,抽離出共同点,放入抽象類别中,如下: publicclassPerson {privateString name;privateString sex; ...... } 其它部分,仍留在原類别中。于是上述程序相当于: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 66 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ //EX03_03 //Person.java package_objects; publicclassPerson{ protectedStringname; protectedStringsex; } //Employee.java package_objects; publicclassEmployeeextendsPerson{ staticclassFee{ publicstaticfloat BasicFee; publicstaticfloat Discount; publicstaticfloatGetTotal(){ return BasicFee * Discount; } } publicEmployee(Stringna,Stringsx){ name=na; sex=sx; } publicvoidSetFee(floatbasic_fee,floatdisc) {Fee.BasicFee =basic_fee; Fee.Discount =disc; } publicvoidDisplay(){System.out.println(name+"'sfee:"+Fee.GetTotal());} } // Customer.java package_objects; publicclassCustomerextendsPerson{ staticclassFee{ publicstaticfloat AnnualFee; publicstaticfloat Discount; publicstaticfloatGetTotal(){return AnnualFee *32.25f*Discount;} } publicCustomer(Stringna,Stringsx){ name=na; sex=sx; } publicvoidSetFee(floatannual_fee,float disc){Fee.AnnualFee =annual_fee/ 32.25f; //ConverttoUS$toNT$ Fee.Discount =disc; } publicvoidDisplay(){System.out.println(name+"'sfee:"+Fee.GetTotal());} } // JMain.java import_objects.*; publicclassJMain{ PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 67 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ publicstaticvoidmain(String[]args){ Employeeemp=newEmployee("Tom","M"); Customercust=newCustomer("Lily","F"); emp.SetFee(1000f,0.9f); cust.SetFee(500f,0.75f); emp.Display(); cust.Display(); }} 至此,抽象的结果是:得到 Person 抽象類别。 3.2.3 细腻的手艺(二):比较函數成员 其步骤如下: Step1: 抽出名称、參數及内容皆一致的函數。 比完了资料成员,接着比较 函數成员。可看出 Employee 和 Customer 兩類别 的建构者函數的參數及内容是一致的,就将之抽象到 Person 父類别裡,如下的 Java 程序: //EX03_04 //Person.java package_objects; publicclassPerson{ protectedStringname; protectedStringsex; publicPerson(Stringna,Stringsx) { name=na; sex=sx; } } //Employee.java package_objects; publicclassEmployeeextendsPerson{ staticclassFee{ publicstaticfloat BasicFee; publicstaticfloat Discount; publicstaticfloatGetTotal() PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 68 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ { return BasicFee * Discount; } } publicEmployee(Stringna,Stringsx) { super(na,sx); } publicvoidSetFee(floatbasic_fee,float disc){Fee.BasicFee =basic_fee; Fee.Discount =disc; } publicvoidDisplay(){System.out.println(name+"'sfee:"+Fee.GetTotal());} } // Customer.java package_objects; publicclassCustomerextendsPerson{ staticclassFee{ publicstaticfloat AnnualFee; publicstaticfloat Discount; publicstaticfloatGetTotal(){return AnnualFee *32.25f*Discount;} } publicCustomer(Stringna,Stringsx) { super(na,sx); } publicvoidSetFee(floatannual_fee,floatdisc){ Fee.AnnualFee =annual_fee/32.25f; //ConverttoUS$toNT$ Fee.Discount =disc; } publicvoidDisplay(){System.out.println(name+"'sfee:"+Fee.GetTotal());} } // JMain.java import_objects.*; publicclassJMain{ publicstaticvoidmain(String[]args){ Employeeemp=newEmployee("Tom","M"); Customercust=newCustomer("Lily","F"); emp.SetFee(1000f,0.9f); cust.SetFee(500f,0.75f); emp.Display(); cust.Display(); }} 此程序输出: Tom's fee: 900.0 Amy'sfee:375.0 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 69 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ Step2: 抽出名称相同、參數及内容有些差異的函 數。 这个步骤较为复杂,其细腻过程如下: ★ 找出相異点。 ★ 运用多形函數來尽量吸收相異点。 ★ 吸收之后,有些原不相同函數,会变成相同了。 ★ 将相同之函數提升到高层類别中。 其关键在于:如何运用多形函數﹖让我们细心介绍这个重要手艺吧!首 先 请看个更简单的 Java 程序范例: AA 類 别 BB 類 别 private String x; publicvoid Print() {System.out.println(x) ; } ..... private int x; publicvoid Print() {System.out.println(x) ; } ..... 资料成员的型态并不相同,找到了相異点: String x;l 相異 int x; 这导致函數的内容也不一样: void Print() { System.out.println(x); } void Print() { System.out.println(x); EndSub 相異 此时,就将相異点封藏起來,那么多形函數就派上用场了﹗兹为兩類别各定义 一个 GetData()函數: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 70 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ AA 類别 privateStringx; publicfinalvoidPrint() { System.out.println(GetData()); } public String GetData() { return x; } ..... BB 類别 privateintx; public final Print() { System.out.println(GetData()); } public String GetData() { return String.valueOf(x); } ..... 于是 ,Print()函數变成为相同点了,可摆入抽象類别中;然后也把 GetData()的 定义摆入抽象類别裡,如下图: SuperAB pubic final void Print() { System.out.println(GetData()); } publicStringGetData(){} 在上述例 子中 ,SuperAB 類 别 的 GetData()裡没 有任何指令,它就相当 于 abstract 抽象函數。所以上述指令: publicStringGetData(){} 就相当于: publicabstractStringGetData(); AA BB privateStringx; privateintx; publicStringGetData(){ returnx; } publicStringGetData(){ returnString.valueOf(x); } PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 71 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 这 例 子已 让 您 了 解:透 过 GetData()卡榫函數将差異点包装起來, 然后将Print()函數抽象出來,摆入抽象父類别裡。其中,Print()函數的參數是 一致的(包括没有參數),只有函數裡的部分指令有差異而已。如果參數有 些差異时,又该如何呢?请您再看个例子吧!如下: //EX03_05 //JMain.java package_objects; publicclassJMain{ privatevoidprint(doublex,inty){ System.out.println(x+y); } privatevoidprint(intk,inty) { System.out.println(k*y); } publicstaticvoidmain(String[]args){ JMainmObj=newJMain(); mObj.print(3.6,6); mObj.print(2,60); }} 请您 練习如何观察这 兩个 print()函數裡的異同,会发现其 參數型态并不相 同。于是,找到了相異点: publicvoidprint(doublex, inty){ 相異 } System.out.println( x+ y ); 相異 publicvoidprint(intk, inty){ System.out.println( k* y ); } 一样地,只要将差異点包装起來,就行了。其步骤如下: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 72 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ Step-2a 建立參數之抽象類别,例如: Number Double Integer 则兩个 print()函數之參數就一致了,如下: publicvoidprint( Numbernumb, inty){ ……… } Step-2b 使用多形函數将内部差異指令包装起來,例如,以 prStr()函數包装 之 后,print()函數成为: publicvoidprint(Numbernumb,inty) {System.out.println( numb.prStr(y) ); } 如下述 Java 程序: //EX03_06 //Number.java package_objects; publicabstractclassNumber{ publicabstractStringprStr(inty); } //jvInt.java package_objects; publicclassjvIntextendsNumber{ privateintx; publicjvInt(inti) { x=i; } publicStringprStr(inty){ returnString.valueOf(x*y); } } //jvFloat.java package_objects; publicclassjvFloatextendsNumber{ PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 73 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ private float x; publicjvFloat(floata) { x=a; } publicStringprStr(inty){ returnString.valueOf(x+y); } } // JMain.java import_objects.*; import_objects.Number; publicclassJMain{ privatevoidprint(Numbernumb,inty) {System.out.println(numb.prStr(y));} publicstaticvoidmain(String[]args) {JMainmObj=newJMain(); jvFloat a=newjvFloat(3.6f); jvIntb=newjvInt(2); mObj.print(a,6); mObj.print(b,60); }} 此程序输出: 9.6 120 接着,继续抽出 print()函數,摆入抽象父類别中,如下: //EX03_07 //Number.java package_objects; publicabstractclassNumber{ publicabstractStringprStr(inty); publicvoidprint(inty){System.out.println(this.prStr(y));} } //jvInt.java package_objects; publicclassjvIntextendsNumber{ privateintx; publicjvInt(inti) { x=i;} publicStringprStr(inty){ returnString.valueOf(x*y); } } //jvFloat.java package_objects; publicclassjvFloatextendsNumber{ PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 74 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ private float x; publicjvFloat(floata) { x=a; } publicStringprStr(inty){ returnString.valueOf(x+y); } } // JMain.java import_objects.*; import_objects.Number; publicclassJMain{ publicstaticvoidmain(String[]args) {JMainmObj=newJMain(); jvFloat a=newjvFloat(3.6f); jvIntb=newjvInt(2); mObj.print(a,6); mObj.print(b,60); }} 现在,请回到前面 EX03-04 的例子吧﹗请您練习观察这兩个函數裡的異同点: publicclassEmployee{ ...... publicvoidDisplay(){ System.out.println(name+"'sfee:"+Fee.GetTotal()); } } 与 publicclassCustomer{ ...... publicvoidDisplay(){ System.out.println(name+"'sfee:"+Fee.GetTotal()); } } 前面已认定 Employee.Fee 類别与 Customer.Fee 類别的内容并不相同, 是相 異之处,这导致兩个 Fee.GetTotal()是 相 異之处。因之,Display()不 能直接摆入 抽象類别中。现在设计一个 Overridable 函數,吸收其相異点, 如下程序: //EX03_08 //Person.java package_objects; publicclassPerson{ PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 75 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ protected String name; protectedStringsex; publicPerson(Stringna,Stringsx){ name=na; sex=sx; } } // Employee.java package_objects; publicclassEmployeeextendsPerson{ staticclassFee{ publicstaticfloat BasicFee; publicstaticfloat Discount; publicstaticfloatGetTotal(){ return BasicFee * Discount; } } publicEmployee(Stringna,Stringsx){ super(na,sx); } publicvoidSetFee(floatbasic_fee,float disc){Fee.BasicFee =basic_fee; Fee.Discount =disc; } publicvoidDisplay(){ System.out.println(name+"'sfee:"+Fee.GetTotal()); }} // Customer.java package_objects; publicclassCustomerextendsPerson{ staticclassFee{ publicstaticfloat AnnualFee; publicstaticfloat Discount; publicstaticfloatGetTotal(){return AnnualFee *32.25f*Discount; } } publicCustomer(Stringna,Stringsx) {super(na,sx); } publicvoidSetFee(floatannual_fee,floatdisc) {Fee.AnnualFee =annual_fee/32.25f; //ConverttoUS$toNT$ Fee.Discount =disc; } publicvoidDisplay(){ System.out.println(name+"'sfee:"+Fee.GetTotal()); }} // JMain.java import_objects.*; publicclassJMain{ PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 76 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ publicstaticvoidmain(String[]args){ Employeeemp=newEmployee("Tom","M"); Customercust=newCustomer("Lily","F"); emp.SetFee(1000f,0.9f); cust.SetFee(500f,0.75f); emp.Display(); cust.Display(); }} 透过 GetFee()的协助, Display()就能飞上枝头变凤凰了。依样画葫蘆,也能 轻易地让另一个函數:SetFee()飞上枝头变凤凰。这就留给您自己練习了。 3.2.4 细腻的手艺(三):将抽象類别转为接口 3.2.4.1 框架裡定义了许许多多的接口 前面說过,框架裡含有抽象類别,而抽象類别裡含有预设的函數,这些 函 數的内容将表现出框架的预设行为。然而,框架设计师经常回发现有些 抽象類 别 不 需提供预设行为,于是抽象類别裡的所有函數都成为抽象函數 (abstractfunction)了。这种函數就是空的函數,只有定义而无实作指令。 此时,这种纯粹的抽象類别就相当于 Java 的界面机制了。例如: publicabstractclassGraph{ //纯粹抽象類别 publicabstractvoiddraw(); publicabstractvoidpaint(); } 这个 Graph 抽象類别,表面上是一个父類别,但在意义上,它代表一 个介 面。所以它也就相当于: //IGraph.java publicinterfaceIGraph{ voiddraw(); voidpaint(); } PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 77 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 那么,为何 Java 既提供 Interface 机制,又提供纯粹抽象類别呢? 这 有一个 歷史因素,在 1997 年以前,主要 OOP 语言 (如 C++)都没有提供 Interface 机制, 那时是以纯粹抽象類别來表达接口,也藉由多重继承來表达 出多重接口。 由于框架裡含有抽象類别,而有些抽象類别并不需要提供预设函數, 于 是 就 以 Java 的 接 口 机 制 表 达 之 。所以框架你会发现框架(如 Android 或.Net)裡定义 了 许许多多的接口。例如,Android 框架裡的 OnClickListener 界面,如下的Android 应用程序代码: //Android 应用程序:MyActivity.java publicclass MyActivity extends Activity implements OnClickListener { /**Calledwhentheactivityisfirstcreated.*/ privatefinalintDB_Version=1; privatefinalintDB_Mode=Context.MODE_PRIVATE; privatefinalintWC=ViewGroup.LayoutParams.WRAP_CONTENT; privateSQLiteDatabasedb=null; privateButtonbtn; @Overridepublicvoid onCreate(Bundleicicle){ super.onCreate(icicle); btn=newButton(this); btn.setText("Exit"); btn.setOnClickListener(this); setContentView(btn,newLinearLayout.LayoutParams(WC,WC)); try{ db=createDatabase("MyDB",DB_Version,DB_Mode,null); } catch(FileNotFoundException e){Log.e("ERROR", e.toString());db=null; } if(db!=null)setTitle("createDBOK!"); else setTitle("Error!"); } publicvoidonClick(Viewv){ if(v.equals(btn)) this.finish(); }} PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 78 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 3.2.4.2 打造框架时,如何抽象出界面呢? 由于接口是纯粹抽象類别,所以能运用本章前面各节所介绍的抽象步 骤和 细腻手艺來抽象出接口。例如有兩个 Java 類别,各代表学生注册領域 裡的:「大学生」与「研究生」概念,如下: //EX03_09 package_objects; publicclass 大学生 //如同石头火锅餐桌 { privateStringName; publicfloatComputeTuition(intcredit){ if(credit>6) credit=6; return(credit-1)*500+5000; }} //BasicFee.java package_objects; publicclass 研究生 //如同砂锅鱼头餐桌 { privateStringName; publicfloatComputeTuition(intcredit){ if(credit>6) credit=6; returncredit*700+5000; }} 其中的 ComputeTuition()函數可计算出大学生或研究生的学费。现在,就运 用本章前面各节所介绍的「抽象」步骤來打造界面。兹比较上述的兩个類别,看 出其不一样之处: 即「大学生」類别裡的指令 -----(credit-1)*500 与「研究生」類别裡的指令 -----credit*700 其余部份则是一样的。这导致兩个 ComputeTuition()不能直接摆入抽象類别 中。 现在设计一个 Overridable 函數:GetValue()抽象函數來封藏之,吸收其 相異点, 就能让 ComputeTuition()飞上枝头变凤凰了,如下程序: //EX03_10 package_student; import_interface.*; PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 3 章 如 何 打造应用框架 79 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ publicclass 学生 { privateStringName; publicfloatComputeTuition(intcredit){ if(credit>6)credit=6; returntc.GetValue(credit)+5000; } protectedabstractfloatGetValue(intcredit); } public class 大学生 extends 学生 { //如同锅子 publicfloatGetValue(intcredit) { return(credit-1)*500; } } publicclass 研究生 extends 学生 { //如同锅子 publicfloatGetValue(intcredit) { returncredit*700; } } 接着,再将抽象函數 GetValue()独立出來,单独摆入一个抽象類别裡,就能为纯 粹抽象類别了,也就能以界面表示出來,如下: //EX03_11 package_interface; publicinterfaceITuition//学费界面 {publicfloatGetValue(intcredit);} package_student; import_interface.*; publicclass 学生 { privateStringName; privateITuitiontc; publicvoidSetter(ITuitiontuiObj) { tc=tuiObj; } publicfloatComputeTuition(intcredit){ if(credit>6)credit=6; returntc.GetValue(credit)+5000; }} package_tuition_plugin; import_interface.*; publicclass 大学生学费 implementsITuition{//如同锅子 publicfloatGetValue(intcredit) { return(credit-1)*500; } } publicclass 研究生学费 implementsITuition{//如同锅子 publicfloatGetValue(intcredit) { returncredit*700; } PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 80 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ } 以上之類别支持 ITuition 接口,于是能将「学生」对象与「大学生学费」小 对象结合起來,此外也能将「学生」与「研究生学费」或其它小对象结合起來, 成为完整的信息应用程序,如下之 JMain 主程序: import_student.*; import_tuition_plugin.*; publicclassJMain{ publicstaticvoidmain(String[]args){ floatt1,t2; 学生 Lily = new 学生(); 大学生学费 under_tui=new 大学生学费 ();Lily.Setter(under_tui); t1 = Lily.ComputeTuition(5); 学生 Peter=new 学生(); 研究生学费 grad_tui=new 研究生学费 ();Peter.Setter(grad_tui); t2 = Peter.ComputeTuition(7); System.out.println("Lily:"+String.valueOf(t1)+",Peter:" +String.valueOf(t2)); }} 此程序计算出大学生 Lily 和研究生 Peter 的学费: Lily:7000,Peter:9200 ◆ 高焕堂 教你最先进的「现代软件分析与设计」,它是 OOP + UML + OOAD + Architecture Design 的一脉相传、精雕细琢,渐臻于完美之境。请阅讀「附 錄 B-4」。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 4 章 Android 应用程序设计的基 础 手 艺 : 12 技 81 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第三篇 有(继承)之以为利 ~~老子.道德经~~ 无 之而得 Android 框架, 有之而得 Android 应用程序。应用程 序裡的子類别继承框架裡的父類别, 是实践有之的惯用手艺。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 82 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第 4 章 Android 应用程序设计 的基础手艺:12 技 4.1 #1:如何建立 Menu 选单 4.2 #2:如何呈现按钮(Button)之 1 4.3 #3:如何呈现按钮(Button)之 2 4.4 #4:如何进行画面布局(Layout) 4.5 #5:如何呈现 List 选单之 1 4.6 #6:如何呈现 List 选单之 2 4.7 #7:如何运用相对性布局(Relative Layout) 4.8 #8:如何运用表格式布局(Table Layout) 4.9 #9:如何动态变换布局 4.10 #10:如何定义自己的 View 4.11 #11:如何定义一组 RadioButton 4.12 #12:一个 Activity 启动另一个 Activity PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 4 章 Android 应用程序设计的基 础 手 艺 : 12 技 83 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 从本章开始,将开始介绍及演練 Android 应用程序开发的基本 36 技 了。 其中,有 35 技是属于 Android 框架之上的 Java 程序开发技巧。而第 36 技则属 于 Android 框架之下的 C 程序开发技巧。兹拿麦当勞的汉堡來做比喻,如下 图 所示: 芝麻:Android 应用程序(共 35 技) 上层面包:Android 框架 牛肉和 Cheese:框架与硬件之间 的 C 组件(第 36 技) 底层面包:硬件组件 并不是 C 组件的技巧较少,而是本书范围的缘故。本书的主角是「应用 程序开发」,所以偏重于芝麻部份的手艺。笔者的第二本书:「Android 应用 软件架构设计」,也已经出版了,敬请阅讀之。 这裡以芝麻比喻应用程序,并不是說应用程序的渺小,而表示它的多 与 香,若能与起司、牛肉互相搭配,更有特别风味。 在 本 章 裡 , 将 直接切入应用程序开发的技巧。如果你还没有安装过 Android SDK 及其 Eclipse 开发环境的话,请你先阅讀本书附錄-A,其内含: ◆ 如何安装 AndroidSDK 及其开发环境 ◆ 如何着手撰写 Android 应用程序 ◆ 如何执行 Android 应用程序 如果你觉得附錄-A 还 不 够 详 细 的 话 ,请你上网tom-kao.blogspot.com或 www.misoo1.com 有更详细的解說。现在就让我们一起來演練 36 技吧!! PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 84 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 4.1 #1:如何建立 Menu 选单? 我想大家都很熟悉 Menu 选单的用途了,本节就來說明如何定义 Android 的 Menu 选单,也演練其操作。 4.1.1 操作情境: 1. 此程序开始执行后,按下就出现选单如下: 2. 如果选取选项,画面标题(Title)区显示出字串:”Insert…”。 3. 再 按 下 显示出选单,如果选取选项 ,提 标 (Title) 区显示出:”Delete…”。 4. 再按下显示出选单,如果选取选项,程序就结束了。 4.1.2 撰写步骤: Step-1: 建立 Android 专案:ex01。 Step-2: 撰写 Activity 的子類别:ex01,其程序代码如下: //----ex01.java 程序代码 ---- package com.misoo.ex01; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; public class ex01 extends Activity { publicstaticfinalint ADD_ID =Menu.FIRST; publicstaticfinalint DELETE_ID =Menu.FIRST +1; PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 4 章 Android 应用程序设计的基 础 手 艺 : 12 技 85 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ public static final int EXIT_ID = Menu.FIRST + 2; @Override publicvoid onCreate(Bundleicicle){ super.onCreate(icicle); setContentView(R.layout.main); } @Override publicboolean onCreateOptionsMenu(Menumenu){ super.onCreateOptionsMenu(menu); menu.add(0, ADD_ID,0,R.string.menu_add); menu.add(0, DELETE_ID,1,R.string.menu_delete); menu.add(0, EXIT_ID,2,R.string.menu_exit); returntrue; } @Override publicboolean onOptionsItemSelected(MenuItemitem){ switch (item.getItemId()){ case ADD_ID: setTitle("Insert..."); break; case DELETE_ID: setTitle("Delete..."); break; case EXIT_ID: finish(); break; } returnsuper.onOptionsItemSelected(item); }} Step-3: 修改/res/values/strings.xml 的内容,更改为: ex01 AddItem DelItem Exit 并储存之。 Step-4: 执行之。 4.1.3 說明: 1. 一开始,框架就反向呼叫 onCreate()函數,也呼叫 onCreateOptionsMenu()。 2. 当你选取选项时,框架会反向呼叫 onOptionsItemSelected()函數。 3. 框架是主角,ex01 類别只是被呼叫的配角,复杂的控制邏辑都为框架所 做掉 了,所以程序代码便得简单清晰了。 4. 呼叫 onCreate()函數时,此函數首先正向呼叫父類别 Activity 的 onCreate()函 數 ,先 执 行 父 類 别的 预 设 行 为 , 然后才执行 ex01::onCreate()函數的附加 行 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 86 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 为。继续执行到 setContentView(R.layout.main)指令时,就去讀取 main.xml 的内容,依据它來进行屏幕画面的布局(Layout),并显示出來。 5. 呼叫 onCreateOptionsMenu() 函 數 时 , 执 行 到 指 令 : menu.add(0, ADD_ID,R.string.menu_add) 就 去 讀 取 /res/values/strings.xml 檔 的内容, 取得字串“AddItem”,显示于画面的选单上。 4.1.4 补充說明(一): ※ 为何子類别 ex01 的 onCreate()要正向呼叫父類别的 onCreate()函數呢? ※ 因为框架的某个函數(不是 Activity::onCreate())呼叫 ex01::onCreate()函數 时, 此 onCreate()函數无法自己完成整个「 create」的任务,而需要父類别的预设 函 數來帮忙,才得以完成之。请看个简单的 Java 程序,你就会明白了。 如下范 例: <> IGraph voidonPaint() voidpaint() <> Shape voidonPaint(){ // 画天空背景 } voidpaint(){onPaint();} 由于框架的 paint()呼叫子類别的 onPaint()函數,但子類别需要父類别來帮 忙 画出背景,所以呼叫了父類别的 onPaint()。如果还不清楚的话,请仔细看下述 程 框架 Bird voidonPaint(){ super.onPaint(); // 画海鸥 } PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 4 章 Android 应用程序设计的基 础 手 艺 : 12 技 87 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 式码就能明了了。 //IGraph.java----界面 publicinterfaceIGraph {voidonPaint(); void paint(); } //-------------------------------------------------------- //Shape.java---- 父類别 importjava.awt.*; publicabstractclassShapeimplements IGraph{Graphicsm_gr; publicShape(Graphicsgr){ m_gr=gr; } publicvoidonPaint(){ // 画天空背景 m_gr.setColor(Color.black); m_gr.fillRect(10,30,200,100); } publicvoidpaint() { onPaint();} } //------------------------------------------------ // Bird.java ---- 子類别 importjava.awt.*; publicclassBirdextendsShape {Graphicsm_gr; publicBird(Graphicsgr){ super(gr); m_gr=gr; } publicvoid onPaint(){ super.onPa int(); // 画图(海鸥)指令 m_gr.setColor(Color.cyan); m_gr.drawArc(30,80,90,110,40,100); m_gr.drawArc(88,93,90,100,40,80); m_gr.setColor(Color.white); m_gr.drawArc(30,55,90,150,35,75); m_gr.drawArc(90,80,90,90,40,80); }} //-------------------------------------------------------- // JMain.java ---- 主程序 importjava.awt.*; importjavax.swing.*; classJPextendsJPanel{ publicvoidpaintComponent(Graphicsgr){ super.paintComponents(gr); PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 88 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ IGraph cc = new Bird(gr); cc.paint(); }} publicclassJMainextendsJFrame{ publicJMain(){ setTitle(""); setSize(350,250); } publicstaticvoidmain(String[]args) {JMainfrm=newJMain(); JPpanel=newJP(); frm.add(panel); frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frm.setVisible(true); }} 此程序画出兩只海鸥如下图: 首先,主程序的指令:cc.paint()呼叫子類别 Bird 的 paint(),但是 Bird 并没 有 paint()函數 ,于是采用父類别 Shape 的预 设 paint()函數。此预设 函 數 呼叫onPaint(),就 反向呼叫了子類别的 onPaint()。请注意,是父類别 paint()呼叫子類 别的 onPaint();并 不是父 類别 onPaint()來 呼叫子類别的 onPaint()。反而是子類别 onPaint()呼叫父類别的 onPaint()。 4.1.5 补充說明(二): ※ 当你修改/res/values/strings.xml 内容之后,记得要存盘,为什么呢?因为 这样 可以更新 R.java 的内容,让 menu.add(0, ADD_ID,R.string. menu_add) 指令能找 到所要的字串。 ※ 请你花一点时间认識一下 R.java 的角色和特性,兹說明如下: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 4 章 Android 应用程序设计的基 础 手 艺 : 12 技 89 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ---- 它(R.java)是連结*.java 的程序代码档案和*.xml 布局档案的中介桥梁。 ---- 在 res/layout/裡含有许*.xml 档案。Eclipse 根据这些.xml 档内容而自 动产 生一个「R」類别(在/src/目錄区裡)。程序员不能用手工去更动 它。 ---- 当这些.xml 档案有更新时,Eclipse 就会在你确认并将*.xml 存档时, 自动 更新它(即 R.java 档案)的内容。 ---- 它是程序裡可使用资源(/res/)的索引,对应到*.xml 档案或字串。让您的 AP 很方便透过它來取得相关的资源。也就是說,*.java 程序代码透过这索 引 就 能 方 便 地取得所需要的资源。例如,在 setContentView (R.layout.main) 指 令裡的R.layout.main 就 是 一 个 索 引项,指引到 main.xml,就使用了 main.xml 所预设的(Default)阳春型画面布局了。 4.2 #2: 如何呈现按钮(Button)之 1 按钮可說是最常用的屏幕控制单元,本节就來說明如何定义 Android 的 Button 按钮,也演練其操作。 4.2.1 操作情境: 1. 此程序一开始,画面出现兩个按钮如下: 2. 如果按下按钮,画面标题(Title)区显示出字串:”thisisOKbutton”。 3. 如果选取,程序就结束了。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 90 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 4.2.2 撰写步骤: Step-1: 建立 Android 专案:ex02。 Step-2: 撰写 Activity 的子類别:ex02,其程序代码如下: //-----ex02.java 程序代码 ----- package com.misoo.ex02; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class ex02 extends Activity implements OnClickListener { @Override publicvoid onCreate(Bundleicicle){ super.onCreate(icicle); setContentView(R.layout.main); Buttonbtn=(Button)findViewById(R.id.button); Buttonbtn2=(Button)findViewById(R.id.button2); btn.setOnClickListener(this); btn2.setOnClickListener(this); } publicvoid onClick(Viewarg0){ switch (arg0.getId()){ case R.id.button: setTitle("thisisOKbutton"); break; case R.id.button2: this.finish(); break; } }} Step-3: 修改/res/layout/main.xml 的内容, 更改为: 并储存之。 Step-4: 执行之。 4.2.3 說明: 1. 框架是主角,它呼叫子類别的 onCreate()函數时,首先正向呼叫父類别 Activity 的 onCreate()函 數 ,先 执 行 父 類 别的 预 设 函 數 ,然后才执行自己(即 ex01) 的 onCreate()函數的指令。继续执行到 setContentView(R.layout.main) 指令时,就 去讀取 main.xml 的内容,依据它來进行屏幕画面的布局 (Layout)。 2. 指令:Buttonbtn=(Button)findViewById(R.id.button); 找出目前的布局(即 R.layout.main)裡的按钮參考,并存入 btn 变數裡,于是 btn 就參考到画面上 id 值为 id/button 的按钮了。 3. 指令:btn.setOnClickListener(this); 这设定按钮事件的处理程序(Event Handler),又称为事件监听者。当使用者按 下 id 值为 id/button 的按钮时,框架必须把事件准确地传递到适当的類 别,并 呼叫所指定的函數。其中的參數:this 就表示此按钮事件必须传递 到 ex02 類别 的对象,也就是目前对象(Current Object)。至于由 ex02 類别的哪一个函數來 处理呢?就是由 OnClickListener 接口所规定的 onClick(View arg0)函數來处 理。 PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 92 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 4. 由于可能有多个按钮,其事件都会传递到 ex02 類别,都由 onClick()函數 负责处理,所以在 onClick()函數裡的 switch 指令藉由按钮 id 值來判断 到底是哪一 个按钮送來的事件。如果是由 id 值为 id/button 的按钮(即 OK)所送來的话,就 在画面标题(Title)区显示出字串:”thisisOKbutton”。 反之,如果是由 id 值 为 id/button2 的按钮(即 Exit)所送來的话,就呼叫父類别的 finish()函數而结 束 目前的画面(即目前的 Activity)。 4.3 #3:如何呈现按钮(Button)之 2 在上一个范例裡,使用了指令:btn.setOnClickListener(this)來指明要将按 钮 的 事 件传递给目前对象的 onClick() 函 數 去 处 理 之 。本范例则 使用指令:btn.setOnClickListener(listener)将按钮的事件传递给 listener 对 象的 onClick()函數 处理之。 4.3.1 操作情境: 1. 首先,此程序的画面出现按钮如下: 2. 如果按下按钮,画面标题(Title)区显示出字串:”thisisOKbutton”。 3. 如果选取,程序就结束了。 4.3.2 撰写步骤: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 第 4 章 Android 应用程序设计的基 础 手 艺 : 12 技 93 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ Step-1: 建立 Android 专案:ex03。 Step-2: 撰写 Activity 的子類别:ex03,其程序代码如下: //----ex03.java 程序代码 ---- package com.misoo.ex03; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class ex03 extends Activity { @Override publicvoid onCreate(Bundleicicle){ super.onCreate(icicle); setContentView(R.layout.main); Buttonbtn=(Button)findViewById(R.id.button); Buttonbtn2=(Button)findViewById(R.id.button2); btn.setOnClickListener(listener); btn2.setOnClickListener(listener2); } OnClickListenerlistener=new OnClickListener(){ publicvoid onClick(Viewv){ setTitle("thisisOKbutton"); } }; OnClickListenerlistener2=new OnClickListener(){ publicvoid onClick(Viewv){ finish(); } }; } Step-3: 修改/res/layout/main.xml 的内容,更改为: PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn 94 Android 应用框架原理与程序设计 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
还剩358页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享pdf获得金币 ] 3 人已下载

下载pdf