第 6 章 Abstract Factory 樣式 137
138 Google Android 設計招式之美 Android 設計招式之美教材下載
第 6 章 Abstract Factory 樣式 139 第 6 章 Abstarct Factory 模式 6.1 Abstract Factory 模式美何在? 6.1.1 大格局的分合自如 6.1.2 不知而亦能用 之实践 6.2 介绍 Abstract Factory 模式 6.3 Android 框架与 Abstract Factory 模式 6.3.1 Abstract Factory 模式范例 6.3.2 Abstract Factory 与 Observer 模式之组合
140 Google Android 設計招式之美 6.1 Abstract Factory 模式美何在? 6.1.1 大格局的分合自如在上一章 ( 第 5 章 ) 的 Observer 模式创造了垂直面的抽换性 ( 或互换性 ), 也就是框架与应用类别的分合自如 ( 或称为 PnP), 其中依赖于 Observer 模式里的抽象类别 ( 即接口 ), 而达到 不知而亦能用 之效果 如下图 : 图 6-1 美好的接口让. 抽象与应用类别分合自如 那么, 如何创造水平方向的抽换性呢? 水平方向的抽换性, 就是 Client 类别与 Server 类别之间的独立性 例如下图 6-2 里, 如果我们抽换掉 Server 应用类别时,Client 类别并不受影响, 因为 Client 类别呼叫 Factory 抽象类别的函数去诞生 Server 之对象 诞生完毕后,Client 类别呼叫 Server 抽象类别的函数去反向呼叫到 Server 应用类别之函数, 就完成了 Client 与 Server 两个类别间之沟通了 由于我们抽换掉 Server 应用类别时, 不会影响到 Client 类别, 所以 Server 应用类别具有高度的抽换性 从下图 6-2 可以看出来, 这种美好效果是来自于 Factory Method
第 6 章 Abstract Factory 樣式 141 模式的贡献 让 Server 应用类别像汽车的轮胎一样, 随时可以汰旧换新 图 6-2 美好的接口让 Client 与 Server 类别分合自如 此图已经展现出 Factory Method 的效益了 然而, 我们还可以继续发挥其魅力, 缔造更大格局的分合自如 此时,Abstract Factory 模式就派上用场了 Abstract Factory 之美将带来大格局的 不知而亦能用 效果, 创造更高的抽换性, 让系统能顺畅新陈代谢 且无限繁荣
142 Google Android 設計招式之美 6.1.2 不知而亦能用不知而亦能用 之实践在前面第 2 3 章里介绍过 Template Method 与 Factory Metrhod 模式的组合, 它们的组合成为当今许多框架设计之基础, 如下图 : 图 6-3 Template Method 与 Factory Metrhod 模式的组合 这是 变与不变分离 原则的重要表现, 基于此结构的反向控制 ( 即卡榫 ) 机制, 让不变部份与会变部份能够随时组合起来 变与不变的 分 离是手段, 随
第 6 章 Abstract Factory 樣式 143 时能结 合 则是目的 所谓 分得妙, 合得快 就是框架设计的精神所在 例如, 在日常生活中, 可以见到汽车的车体与轮胎是分离的, 因为轮胎是年年都得换新 ( 会变 ), 而车体十年不变, 两者分离而得出轮盘 ( 即卡榫 ) 车体与轮胎的分离是手段, 随时能将轮胎换新 ( 重新结合 ) 才是目的 降低 Cleint 与 Server 之相依性 在上图里,ConcreteClient 类别的 FactoryMethod() 函数内有个指令 : obj = new ConcreteProduct(); 它使用了 "ConcreteProduct" 字眼, 这造成 ConcreteClient 与 ConcreteProduct 两个类别之间的高度相依性 (Dependency) 此时, 可以增添一个 ConcreteFactory 类别, 将 ConcreteClient 与 ConcreteProduct 两类别分隔开来 如下图 :
144 Google Android 設計招式之美 图 6-4 创造 不知而亦能用 效果 兹比较图 6-3 与图 6-4 图 6-3 的不完美之处为 : 必须先撰写 ConcreteProduct 类别 ( 并决定此类别之名称 ), 之后, 才能完成 ConcreteClient 类别之撰写 当 ConcreteProduct 类别名称有所更改时, 也必须更改 ConcreteClient 类别之内容 图 6-4 的设计就精致多了 ( 表面上看来是复杂多了 ) 于图 6-4 里, 无论是 Client 或 ConcreteClient 类别里都已经没有 "ConcreteProduct" 字眼了 在未来的时日里,
第 6 章 Abstract Factory 樣式 145 当 ConcreteProduct 类别名称需要变更, 并不必更改 ConcreteClient 类别之内容 当人们在撰写 Client 或 ConcreteClient 类别的程序码时, 只需要知道 ConcreteFactory 类别 ( 含名称 ), 并不须要知道 ConcreteProduct 类别 ( 含名称 ), 这就称为 不知而亦能用 之效果 所谓 不知而亦能用, 就是对象 A 的设计者, 脑筋只思考到对象 B 的接口 (What) 就行了, 不需要知道对象 B 的实作 (How-to) 内涵, 达到不知 ( 实作 ) 而亦能用的佳境 就像您不知道 Intel CPU 的内部设计, 只知道其接口就能使用之 请记得,Intel CPU 内有 Intel's Design Inside! 其内部实作细节价值连城, 可以让别人拥有 CPU( 也知道 CPU 接口用法 ), 但不能让别人拥有 CPU 内部的设计 (Design) 思维! 不知而亦能用, 就意味着很容易抽换 美好的接口能降低实作面的相依性, 促进系统的新陈代谢 所以 GoF 的 <<Design Patterns> 一书里说到 : This so greatly reduces implementation dependencies between subsystems that it leads to the following principles of reusable object-oriented design: Program to an interface, not an implementation. ( 这能大幅降低子系统之间的实作相依性, 因而导出一项屡试不爽的 OOD 设计原则 : 针对接口而写程序, 不要针对实作内涵 ) 当您在设计应用框架时, 别忘了, 拿这句话当做为您的座右铭, 再适当不过了 维持变与不变之分离 所谓 设计 (Design), 就是解开 (De) 既有的结构 (Sign) 的意思 刚才已经将 ConcreteClient 与 ConcreteProduct 两者之相依性化解了, 此时产生了一点点小的副作用, 就是变与不变分离的美好状态被破坏了 其原因是, 在属于框架的 Client( 不变 ) 抽象类别里,fa 数据的型态是 ConcreteFactory, 而 ConcreteFactory 类别又属于应用程序 ( 会变 ) 部分, 其 "ConcreteFactory" 字眼是会变的 因而, 框架里含有会变部份, 有违变与不变分离原则 这是框架设计与开发过程中经常会出现的情形, 只要加以调整, 让它恢复变与不变分离的美好状态就行了 于是, 针对 ConcreteFactory 类别进行变与不变分离, 抽象出父类别并摆入框架里, 就恢复变与不变分离的美好状态了 如下图 :
146 Google Android 設計招式之美 图 6-5 更精致的框架设计 看到上图, 许多人常被其外表所迷惑了, 认为从图 6-3 到图 6-5 是变复杂了, 怎么会是变精致了呢? 这就更凸显设计模式之美的重要性了 当我们心中怀有 Factory Method 模式时, 就会觉得上图 6-5 是精致而不是复杂了 现在, 兹以 Java 程序码来实现上述的图 6-5 << 撰写程序 >>
第 6 章 Abstract Factory 樣式 147 Step-1. 建立一个 Java 应用程序项目 :Ex06-01 Step-2. 撰写 AF 的各类别 // Product.java public abstract class Product { public void template_method() { hook_method(); } protected abstract void hook_method(); } // Factory.java public abstract class Factory { public abstract Product createproduct(); } // Client.java public abstract class Client{ protected Factory fa; private Product obj; public void AnOperation(){ FactoryMethod(); obj = fa.createproduct(); obj.template_method(); } public abstract void FactoryMethod(); } Step-3. 撰写 AP 的各类别 // ConcreteProduct.java public class ConcreteProduct extends Product { protected void hook_method() { System.out.println("ConcreteProduct..."); }}
148 Google Android 設計招式之美 // ConcreteFactory.java public class ConcreteFactory extends Factory{ public ConcreteProduct createproduct() { return new ConcreteProduct(); }} // ConcreteClient.java public class ConcreteClient extends Client { public void FactoryMethod() { fa = new ConcreteFactory(); }} Step-4. 撰写 JMain 类别 // JMain.java public class JMain { public static void main(string[] args) { Client c = new ConcreteClient(); c.anoperation(); }} 此应用程序实现了上图 6-5 的结构 它含有两项特色 : 因为框架里的类别都没有用到应用类别及其数据或函数之名称, 所以可以开发在先, 而后才开发应用类别 这也是 变与不变分离 之美妙效果 因为 ConcreteClient 类别没有用到 ConcreteProduct 类别之名称 ( 即不知而亦能用 ), 所以负责撰写 ConcreteProduct 类别的开发者能随时更改此类别的名称, 而不会牵动 ConcreteClient 类别 如此创造了 ConcreteProduct 类别名称变动的自由度, 也就是高度的互换性
第 6 章 Abstract Factory 樣式 149 6.2 介绍 Abstract Factory 模式 由于一个父类别能够衍生出多个子类别, 所以上图 6-5 的美好结构可以加以扩充, 如下图 : 图 6-6 美好结构的扩充 这将美好结构加以扩充到整个 Product 类别继承体系 在 GoF 的 <<Design Patterns>> 一书里, 称之为 Abstract Factory 模式, 如下图所示 :
150 Google Android 設計招式之美 图 6-7 GoF 的 Abstract Factory 模式图 上图的 Client 只用到父类别 AbstractProductA 和 AbstractProductB, 而不会用到其子类别 ( 如 ProductA1 ProductA2 等 ) 的类别名称 数据名称或函数名称 因此,Client 与 Product 类别体系的子类别之间是低度相依 (Loosely-coupled) 的, 这是使用 Abstract Factory 模式时所获得的美好效果
第 6 章 Abstract Factory 樣式 151 6.3 Android 框架与 Abstract Factory 模式 6.3.1 Abstract Factory 模式范例在 Android 里,Activity 的子类别通常扮演 Client 的角色 而 Service 的子类别常常扮演着启动后端服务模块 ( 例如 MediaPlayer 模块 ) 的任务, 其角色与 Abstract Factory 模式的 Factory 类别是一致的, 如下图所示 :
152 Google Android 設計招式之美 图 6-8 Android 的 Abstract Factory 模式之例 兹撰写一个 Android 应用程序来实现上图 6-8 << 操作情境 >> 此程序执行时, 呈现如下画面 : << 撰写程序 >> Step-1. 建立一个 Android 应用程序项目 :Ex06-02 Step-2. 撰写 mp3player 类别 // mp3player.java package com.misoo.pkzz; import android.content.context; import android.media.mediaplayer; import android.os.binder; import android.os.parcel; import android.util.log;
第 6 章 Abstract Factory 樣式 153 public class mp3player extends Binder{ private MediaPlayer mplayer = null; private Context ctx; public mp3player(context cx){ ctx= cx; } @Override public boolean ontransact(int code, Parcel data, Parcel reply, int flags) throws android.os.remoteexception { if(code == 1) this.play(); else if(code == 2) this.stop(); return true; } public void play() { if(mplayer!= null) return; mplayer = MediaPlayer.create(ctx, R.raw.test_cbr); try { mplayer.start(); } catch (Exception e) { Log.e("StartPlay", "error: " + e.getmessage(), e); } } public void stop(){ if (mplayer!= null) { mplayer.stop(); mplayer.release(); mplayer = null; } }} Step-3. 撰写 mp3service_factory 类别 // mp3service_factory.java package com.misoo.pkzz; import android.app.service; import android.content.intent; import android.os.ibinder; public class mp3service_factory extends Service { private IBinder mbinder = null; @Override public void oncreate() { mbinder = new mp3player(getapplicationcontext()); } @Override public IBinder onbind(intent intent) { return mbinder; }} Step-4. 撰写 Client 类别 // Client.java package com.misoo.pkzz; import android.app.activity; import android.content.componentname; import android.content.context; import android.content.intent;
154 Google Android 設計招式之美 import android.content.serviceconnection; import android.graphics.color; import android.os.bundle; import android.os.ibinder; import android.os.remoteexception; import android.view.view; import android.view.view.onclicklistener; import android.widget.button; import android.widget.linearlayout; import android.widget.textview; public class Client extends Activity implements OnClickListener { private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT; private final int FP = LinearLayout.LayoutParams.FILL_PARENT; private static Client appref = null; private Button btn, btn2, btn3; public TextView tv; private IBinder ib; public static Client getapp() { return appref; } public void btevent(string data) { settitle(data); } public void oncreate(bundle icicle) { super.oncreate(icicle); appref = this; LinearLayout layout = new LinearLayout(this); layout.setorientation(linearlayout.vertical); btn = new Button(this); btn.setid(101); btn.settext("play"); btn.setbackgroundresource(r.drawable.heart); btn.setonclicklistener(this); LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(90, 45); param.topmargin = 10; layout.addview(btn, param); btn2 = new Button(this); btn2.setid(102); btn2.settext("stop"); btn2.setbackgroundresource(r.drawable.heart); btn2.setonclicklistener(this); layout.addview(btn2, param); btn3 = new Button(this); btn3.setid(103); btn3.settext("exit"); btn3.setbackgroundresource(r.drawable.exit_blue); btn3.setonclicklistener(this); layout.addview(btn3, param); tv = new TextView(this); tv.settextcolor(color.white); tv.settext("ready"); LinearLayout.LayoutParams param2
第 6 章 Abstract Factory 樣式 155 = new LinearLayout.LayoutParams(FP, WC); param2.topmargin = 10; layout.addview(tv, param2); setcontentview(layout); //---------------------------------------------------------------------------------------------- startservice(new Intent(this, mp3service_factory.class)); bindservice(new Intent(Client.this, mp3service_factory.class), mconnection, Context.BIND_AUTO_CREATE); } private ServiceConnection mconnection = new ServiceConnection() { public void onserviceconnected(componentname classname, IBinder ibinder) { ib = ibinder; } public void onservicedisconnected(componentname classname) {} }; public void onclick(view v) { switch (v.getid()) { case 101: tv.settext("playing audio..."); settitle("mp3 Music"); try { ib.transact(1, null, null, 0); } catch (RemoteException e) { e.printstacktrace(); } break; case 102: tv.settext("stop"); try { ib.transact(2, null, null, 0); } catch (RemoteException e) { e.printstacktrace(); } break; case 103: finish(); break; } }} << 说明 >> 说明此程序的 Activity 抽象类别反向呼叫到其 Client 子类别的 oncreate() 函数 此函数里的指令 : startservice(new Intent(this, mp3service_factory.class)); 就启动了 Service 抽象类别, 反向呼叫了 mp3service_factory 子类别的 oncreate() 函数, 诞生了 mp3player 类别之对象 再来执行到指令 : bindservice(new Intent(Client.this, mp3service_factory.class), mconnection, Context.BIND_AUTO_CREATE);
156 Google Android 設計招式之美 在透过 Service 抽象类别, 反向呼叫了 mp3service_factory 子类别的 onbind() 函数, 取得了 mp3player 对象的接口参考 (Reference) 值 此时会反向呼叫 Client 类别里的 onserviceconnected() 函数 : private ServiceConnection mconnection = new ServiceConnection() { public void onserviceconnected(componentname classname, IBinder ibinder) { ib = ibinder; } public void onservicedisconnected(componentname classname) {} }; 就将 mp3player 对象的接口参考值回传给 ib 变量 于是,ib 就参考到 mp3player 对象的接口 ( 即 IBinder) 了 此时,Service( 以及 mp3service_factory 类别 ) 父 子类别就功成身退了 他们就展现了 Abstract Factory 模式的结构及功能, 替 Client 诞生了 mp3player 对象, 并且传回其参考值 现在,Client 已经取得 mp3player 对象之接口参考值了 接下来, 使用者按下 <play> 按钮时, 就反向呼叫到 onclick() 函数, 其内之指令 : ib.transact(1, null, null, 0); 就呼叫到 IBinder 接口里的 transact() 函数, 进而反向呼叫到 mp3player 的 ontransact() 函数 请留意,Client 类别只使用到 IBinder 接口而已, 并未使用到 Binder mp3player 等的类别名称 变量名称或函数名称, 维持了 Binder mp3player 等类别的高度的变动自由度, 也实现了 不知而亦能用 之效果 6.3.2 Abstract Factory 与 Observer 模式之组合虽然上述的 Android 范例程序, 主要是介绍如何心中怀着 Abstract Factory 模式而去欣赏 Android 的 Service 父子类别与 Binder 父子类别之复杂关系 ( 表面看来 ) 然而, 你还可以看到 Abstract Factory 模式与 Observer 模式的联合运用 例如下述之指令 :
第 6 章 Abstract Factory 樣式 157 bindservice(new Intent(Client.this, mp3service_factory.class), mconnection, Context.BIND_AUTO_CREATE); private ServiceConnection mconnection = new ServiceConnection() { public void onserviceconnected(componentname classname, IBinder ibinder) { ib = ibinder; } public void onservicedisconnected(componentname classname) {} }; 这就是 Observer 模式的运用, 让 Service 类别能反向呼叫回来, 才能顺利把 mp3player 对象的接口参考值回传给 Client 类别 此外, 从指令 : btn2.setonclicklistener(this); 也可以看出来它也是 Observer 模式之运用 Android 架构之美就是来自于多种设计模式的美妙组合 無論在邊疆 城市 海島 山區 鄉下, 都能學到最好的 Android 架構體系知識 請聽聽高煥堂老師的在線 (online) 課程,100 小時課程僅收 RMB 2250 元, 高老師 37 年來軟件編程功力和精華盡在其中 高老師還錄製了 18 小時, 非常珍貴的 " 架構師的策略思考 " 視頻, 放入上述 100 小時的 on-line 課程裡, 歡迎試聽 :