Android設計模式之外觀模式在項目中的實際使用總結

前言

外觀模式在開發中使用頻率非常高。我們常常使用的第三方和開源庫 基本都會使用外觀模式。通過一個外觀類使得整個系統的接口只有一個統一高層接口,這樣利於降低用戶的使用成本,也對用戶屏蔽了很多實現細節,項目更容易維護。

在使用第三方SDK和開源庫時,可能最大的使用思想就是封裝,封裝第三方的API,同時可能還會用到其他設計模式如策略模式,方便隨時替換第三方SDK。

本文會從理論和實踐進一步深入總結外觀模式在項目中的運用。

外觀模式的定義

外觀模式(Facade Pattern)又叫門面模式。

要求一個子系統的外部與其內部的通信必須通過一個統一的對象進行。門面模式提供一個高層次的接口,使得子系統更易於使用。

外觀模式的使用場景

  1. 在設計初期階段,將不同的兩個層分離;
  2. 在開發階段,子系統往往因爲不斷的重構演化而變得越來越複雜,大多數的模式使用時也都會產生很多很小的類,這本是好事,但也給外部調用它們的用戶程序帶來了使用上的困難,增加外觀Facade可以提供一個簡單的接口,減少它們之間的依賴。
  3. 在維護一個遺留的大型系統時,可能這個系統已經非常難以維護和擴展了,但因爲它包含非常重要的功能,新的需求開發必須依賴於它。

外觀模式的 UML 類圖

在這裏插入圖片描述

角色介紹

  • Client : 客戶端程序。
  • Facade : 對外的統一入口,即外觀對象。
  • SubSystemA : 子系統A。
  • SubSystemB : 子系統B。
  • SubSystemC : 子系統C。
  • SubSystemD : 子系統D。

不使用外觀對象

在這裏插入圖片描述
從圖中可以看到 在沒有外觀類後這個統一接口後,客戶端會和每一個子系統進行交互,增加了調用者的使用成本,項目維護成本也增加。

外觀模式的簡單示例實現

生活中的外觀模式主要體現在 類似中央調度結構的組織,如地鐵的調度中心、公司的項目負責人、軟件公司的技術組長等等。也比如,電視遙控器,控制電視的開關機、播放、聲音調節等,再如手機,集萬千功能於一身等等。

下面以軟件開發的流程中的技術組長(Leader)爲例,用代碼來實現。

1.開發人員,負責開發任務。

/**
 * 開發人員
 */
class Developer {
    fun develop(name: String) {
        println("開發任務:$name")
    }
}

2.測試人員,負責測試需求。

/**
 * 測試人員
 */
class Tester {

    fun test(name: String) {
        println("測試任務:$name")
    }
}

3.產品經理(PM)負責提需求,同時也負責跟進項目的開發、測試和項目的上線。把項目落地。

/**
 * 產品經理, 對接開發和測試
 */
class ProductManager {
    private val developer = Developer()
    private val tester = Tester()

    fun demand(name: String) {
        println("提需求:$name")
        developer.develop(name)
        tester.test(name)
    }
}

測試代碼:

fun main() {
    val demander = ProductManager()
    demander.demand("開發一個跟淘寶一樣的系統")
}
打印結果:
提需求:開發一個跟淘寶一樣的系統
開發任務:開發一個跟淘寶一樣的系統
測試任務:開發一個跟淘寶一樣的系統

上面的較色只有三個(開發、測試和需求),上述代碼沒有引入外觀模式。

公司小的時候,項目溝通基本上是老闆扁平化管理,直接和開發、測試溝通,沒有過多的組織層級。項目初期,這樣做成本低,也能保證項目落地。但是到發展後期,項目變得複雜,需求變化頻繁時,項目的規範就顯得尤爲重要。這時引入開發小組負責人(Leader)就變得很迫切,Leader可以直接和產品需求、測試和開發人員溝通,控制整個開發流程進度,可有效提升開發效率。

下面看看如何在之前的代碼基礎上引入外觀模式。

首先添加一個角色(Leader),相當於負責整個開發流程中的溝通協調和進度把控。

/**
 * 技術組長 Leader (統一的外觀類)
 * Leader負責統一協調開發資源,內部資源的協調不需要外部知道
 */
class Leader {
    private val developer = Developer()
    private val tester = Tester()

    fun processDemand(name: String) {
        developer.develop(name)
        tester.test(name)
    }
}

產品經理(PM)的工作方式 得到改進。之前要頻繁溝通開發和測試兩個角色,現在主要和Leader溝通就可以了。

/**
 * 產品經理直接對接技術組長Leader
 */
class ProductManager2 {
   private val leader = Leader()

    fun demand(name: String) {
      leader.processDemand(name)
    }
}

測試代碼:

fun main() {
    val demander = ProductManager2()
    demander.demand("開發一個跟微信一樣的系統")
}
//打印結果:
提需求:開發一個跟微信一樣的系統
開發任務:開發一個跟微信一樣的系統
測試任務:開發一個跟微信一樣的系統

這個就是我們的外觀模式,我們的技術組長(Leader)就是外觀模式的象徵,他專門對外提供接收需求服務,然後安排需求給開發同學和測試同學,保證完成。

Android源碼中的外觀模式實現

在開發過程中,Context是最重要的一個類型。它封裝了很多重要的操作,比如startActivity()、sendBroadcast()等,幾乎是開發者對應用操作的統一入口。Context是一個抽象類,它只是定義了抽象接口,真正的實現在ContextImpl類中。它就是今天我們要分析的外觀類。

在應用啓動時,首先會 fork一個子進程,並且調用ActivityThread.main方法啓動該進程。ActivityThread又會構建Application對象,然後和Activity、ContextImpl關聯起來,然後再調用Activity的onCreateonStart、onResume函數使Activity運行起來。我們看看下面的相關代碼:

private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
		// 代碼省略

        // 1、創建並且加載Activity,調用其onCreate函數
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            // 2、調用Activity的onResume方法,使Activity變得可見
            handleResumeActivity(r.token, false, r.isForward);

        }
    }

     private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
		// 代碼省略

        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            // 1、創建Activity
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            r.intent.setExtrasClassLoader(cl);
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            // 2、創建Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
                // ***** 構建ContextImpl  ****** 
                ContextImpl appContext = new ContextImpl();
                appContext.init(r.packageInfo, r.token, this);
                appContext.setOuterContext(activity);
                // 獲取Activity的title
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mConfiguration);
            
                 // 3、Activity與context, Application關聯起來
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstance,
                        r.lastNonConfigurationChildInstances, config);
				// 代碼省略

                // 4、回調Activity的onCreate方法
                mInstrumentation.callActivityOnCreate(activity, r.state);
           
                // 代碼省略
            }
            r.paused = true;

            mActivities.put(r.token, r);

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
      
        }

        return activity;
    }


    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
   
        unscheduleGcIdler();

        // 1、最終調用Activity的onResume方法
        ActivityClientRecord r = performResumeActivity(token, clearHide);
        // 代碼省略
        // 2、這裏是重點,在這裏使DecorView變得可見
        if (r.window == null && !a.mFinished && willBeVisible) {
                // 獲取Window,即PhoneWindow類型
                r.window = r.activity.getWindow();
                // 3、獲取Window的頂級視圖,並且使它可見
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                // 4、獲取WindowManager
                ViewManager wm = a.getWindowManager();
                // 5、構建LayoutParams參數
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    // 6、將DecorView添加到WindowManager中,最終的操作是通過WindowManagerService的addView來操作
                    wm.addView(decor, l);
                }
            } else if (!willBeVisible) {
                if (localLOGV) Slog.v(
                    TAG, "Launch " + r + " mStartedActivity set");
                r.hideForNow = true;
            }
            // 代碼省略
    }

 public final ActivityClientRecord performResumeActivity(IBinder token,
            boolean clearHide) {
        ActivityClientRecord r = mActivities.get(token);
       
        if (r != null && !r.activity.mFinished) {
                try {
                // 代碼省略
                // 執行onResume
                r.activity.performResume();
				// 代碼省略
            } catch (Exception e) {
   
            }
        }
        return r;
    }

Activity啓動之後,Android給我們提供了操作系統服務的統一入口,也就是Activity本身。這些工作並不是Activity自己實現的,而是將操作委託給Activity父類ContextThemeWrapper的mBase對象,這個對象的實現類就是ContextImpl ( 也就是performLaunchActivity方法中構建的ContextImpl ())。

class ContextImpl extends Context {
    private final static String TAG = "ApplicationContext";
    private final static boolean DEBUG = false;
    private final static boolean DEBUG_ICONS = false;

    private static final Object sSync = new Object();
    private static AlarmManager sAlarmManager;
    private static PowerManager sPowerManager;
    private static ConnectivityManager sConnectivityManager;
    private AudioManager mAudioManager;
    LoadedApk mPackageInfo;
    private Resources mResources;
    private PackageManager mPackageManager;
    private NotificationManager mNotificationManager = null;
    private ActivityManager mActivityManager = null;
    
	// 代碼省略
    
        @Override
    public void sendBroadcast(Intent intent) {
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            ActivityManagerNative.getDefault().broadcastIntent(
                mMainThread.getApplicationThread(), intent, resolvedType, null,
                Activity.RESULT_OK, null, null, null, false, false);
        } catch (RemoteException e) {
        }
    }
    
    
        @Override
    public void startActivity(Intent intent) {
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1);
    }
    
    
        @Override
    public ComponentName startService(Intent service) {
        try {
            ComponentName cn = ActivityManagerNative.getDefault().startService(
                mMainThread.getApplicationThread(), service,
                service.resolveTypeIfNeeded(getContentResolver()));
            if (cn != null && cn.getPackageName().equals("!")) {
                throw new SecurityException(
                        "Not allowed to start service " + service
                        + " without permission " + cn.getClassName());
            }
            return cn;
        } catch (RemoteException e) {
            return null;
        }
    }
    
        @Override
    public String getPackageName() {
        if (mPackageInfo != null) {
            return mPackageInfo.getPackageName();
        }
        throw new RuntimeException("Not supported in system context");
    }
}

可以看到,ContextImpl內部有很多xxxManager類的對象,也就是我們上文所說的各種子系統的角色。ContextImpl內部封裝了一些系統級別的操作,有的子系統功能雖然沒有實現,但是也提供了訪問該子系統的接口,比如獲取ActivityManager的getActivityManager方法。

比如我們要啓動一個Activity的時候,我們調用的是startActivity方法,這個功能的內部實現實際上是Instrumentation完成的。ContextImpl封裝了這個功能,使得用戶根本不需要知曉Instrumentation相關的信息,直接使用startActivity即可完成相應的工作。其他的子系統功能也是類似的實現,比如啓動Service和發送廣播內部使用的是ActivityManagerNative等。ContextImpl的結構圖如下 :

在這裏插入圖片描述
外觀模式非常的簡單,只是封裝了子系統的操作,並且暴露接口讓用戶使用,避免了用戶需要與多個子系統進行交互,降低了系統的耦合度、複雜度。如果沒有外觀模式的封裝,那麼用戶就必須知道各個子系統的相關細節,子系統之間的交互必然造成糾纏不清的關係,影響系統的穩定性、複雜度。

外觀模式在項目中的運用實踐

  1. 圖片加載加載庫 ImageLoder,封裝了內部的網絡請求、緩存、加載成功或失敗的幾種模式細節等等。
  2. 友盟統計SDK中的 MobclickAgent,封裝了內部的統計功能細節等等。

外觀模式的優點與缺點

優點

  • 使用方便,使用外觀模式客戶端完全不需要知道子系統的實現過程;
  • 降低客戶端與子系統的耦合,方便擁抱變化 ;
  • 更好的劃分訪問層次;

缺點

  • 外觀類接口膨脹。由於子系統的接口都有外觀類統一對外暴露,使得外觀類的API接口較多,在一定程度上增加了用戶使用的使用成本;
  • 在不引入抽象外觀類的情況下,增加新的子系統可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”;

總結

外觀模式的核心思想在於封裝,目的在於統一編程接口。通過一個高層次結構爲用戶提供統一的接口API入口,使得用戶通過一個類型就能夠操作整個系統,這樣減少了用戶的使用成本,提升了系統的靈活性。

參考資料:

1.何紅輝,關愛民. Android 源碼設計模式解析與實戰[M]. 北京:人民郵電出版社

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章