標籤:原廠模式 android 單例模式 登入式單例 業務隔離
Android中寫應用,一樣需要考慮到降低耦合性的問題,還有一些其他問題,比如App的增量式更新,業務變更的便捷實現等等,都會有原廠模式和單例的身影。
原廠模式是我們最常用的執行個體化對象模式了,是用Factory 方法代替new操作的一種模式。因為原廠模式就相當於建立執行個體對象的new,我們經常要根據類Class產生執行個體對象,如A a=new A() 原廠模式也是用來建立執行個體對象的,所以以後new時就要多個心眼,是否可以考慮使用原廠模式,雖然這樣做,可能多做一些工作,但會給你系統帶來更大的可擴充性和盡量少的修改量。
這裡就用一個小例子來說明。
假設我們現在要寫一個登陸的業務功能,按照原廠模式的思路,我們不會直接去寫一個登陸的類,然後將它new出來去調用login的登陸方法,而是會先寫一個介面,介面裡有一個方法名login,然後寫一個介面的實作類別,實現login方法,然後再去將其new出來,這樣做的好處在於,如果實作類別改變了,介面不需要動,不會影響上層介面。假設我們這裡的類分別是:User使用者類,IUserEngine登陸業務介面類和UserEngineImpl登陸業務介面實作類別。
到這裡,貌似原廠模式差不多了?我們寫一個TestCase,就可以直接實現登陸功能了?如下:
public void testLogin() {User user = new User();user.setUserName("alex");user.setPasswd("123456");IUserEngine engine = new UserEngineImpl();engine.login(user);}
看似沒有問題,但是其中卻隱含了大問題。。
試想,如果你剛寫完這個幾百行的login方法,test也通過了,正在洋洋得意的時候,老大說,登陸業務要大改。。。怎麼辦?把剛寫好的幾百行代碼刪了重來?等你費了老大勁改完,通過,沒準老大又說,恩 ,還是之前的好。。。
所以最好的辦法是不動原來的代碼,很多人一定想的到,那不就直接重新寫一個介面實作類別叫做:UserEngineImpl2不就行了?確實,保留原來的那個UserEngineImpl不動,只是不用它。看上去是比之前好一些,但是還是不滿意,假如之前UserEngineImpl在幾百個地方被調用,那不得一個個去改?所以還是麻煩。。。
所以這裡最好以不變應萬變,什麼是不變的,就是那個介面IUserEngine!我們不妨將IUserEngine的名字與它對應的實作類別的完整類名對應起來,儲存在一個設定檔裡面,每次我們都去設定檔中讀取到底是要載入哪一個實作類別,於是如果業務變更,需要修改實作類別的時候,只需要更新一個實作類別,並且改一下設定檔中的一行代碼即可,實現如下:
BeanFactory:用來載入properties設定檔,並且實現擷取對應的類的執行個體。而本身BeanFactory也只需要一個執行個體即可,所以採用單例模式,這裡使用登記式單例:
package com.example.factorymode.util;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.Properties;import com.example.factorymode.bean.User;import com.example.factorymode.engine.IUserEngine;public class BeanFactory {private static Properties properties;// 用於登記式單例模式存放BeanFactory唯一執行個體private static Map<String, BeanFactory> map = new HashMap<String, BeanFactory>();private static String beanFactoryName = BeanFactory.class.getName();static {// 單例模式初始化BeanFactory bFactory = new BeanFactory();map.put(BeanFactory.class.getName(), bFactory);// 初始化設定檔載入,只需要載入一次即可properties = new Properties();try {properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("config.properties"));} catch (IOException e) {e.printStackTrace();}}/** * 單例模式 ①提供一個私人的建構函式,使得外界不能去new ②提供一個getInstance方法,給外界提供本類自己的唯一一個執行個體 * * @return */private BeanFactory() {}// 給外界提供執行個體,保證唯一性,這裡採用登記式單例模式public static BeanFactory getInstance() {if (map.get(beanFactoryName) == null) {try {map.put(beanFactoryName,(BeanFactory) Class.forName(beanFactoryName).newInstance());} catch (InstantiationException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}}return map.get(beanFactoryName);}/** * 載入需要是實作類別 * * @param clazz 介面的類 * @return */public <T> T getImpl(Class<T> clazz) {String simpleName = clazz.getSimpleName();String implClassName = properties.getProperty(simpleName);try {return (T) Class.forName(implClassName).newInstance();} catch (Exception e) {e.printStackTrace();}return null;}}
我們這裡的properties檔案放在src目錄下,這裡便於使用類載入器來載入。
config.properties內容:
IUserEngine=com.example.factorymode.engine.impl.UserEngineImpl
然後我們在TestCast裡就可以這樣來寫:
public void testLogin2(){User user = new User();user.setUserName("alex");user.setPasswd("123456");IUserEngine engine = BeanFactory.getInstance().getImpl(IUserEngine.class);engine.login(user);}
經過上述改造,遇到業務變更的時候,就可以更從容,而且也可以應用到App的增量式更新,比如只有登入業務改變的時候,更新APP的時候就只需要更新一個設定檔,和新增一個登入實作類別即可讓載入時載入到新增的實作類別來產生對應的執行個體,從而節省使用者的流量。
淺談應用原廠模式和單例在Android中實現業務隔離