Overview of Content
這篇文章將介紹單例模式在物件導向設計中的重要性以及多種實現方法。
首先,我們將探討單例模式的UML結構,並詳細討論單例模式的特性和實現方法的優缺點。在實現方法部分,我們將討論幾種常見的單例實現方法,包括 Eager Initialization
(餓漢模式)、Lazy Initialization
(懶漢模式)、雙鎖機制(Double Check Lock
)、靜態內部類和列舉單例。
此外,我們還將探討單例模式在Android源碼中的應用,包括如何取得LayoutInflater、如何包裝Activity的Context,以及分析ContextImpl與SystemServiceRegistry之間的關係。通过本文,读者将深入了解單例模式的各種實現方式以及在Android開發中的應用
Singleton UML
Singleton 實做特性
A. construct 必須設定為 private,不讓使用者隨意創建目標物件
B. 通過靜態方法、成員、列舉返回單例物件
C. 注意使用場景,別注意 多執行序的安全性 問題
D. 確保單例物件在 反序列化 時,不會重新建構物件
單例設計:優缺點
● 單例設計優點
● 最大的特點就是 減少對內存的使用,避免重複創建
● 可以設定該 APP 進程的全局訪問點
● 單例設計缺點
● 不容易符合開閉原則:單例要拓展通常需要手動修改,一般來講也不會有 interface
● 建議不要對內部傳入 Context,否則會造成內存無法回收的問題(由於是單例,可達性分析總會分析到 static 仍在使用,導致 GC 無法回收沒有要用的 Activity)
● 建議傳入 Application#context 來使用,因為 Application#context 的生命週期同 App 進程,所以不被回收也沒關係
Singleton 實現方法
其特色都有相似的點,目的是為了不要有多個物件,要使用該物件都必須使用同一個物件,在這個條件下在考慮其性能,而每種實現方式都有不同特色
Eager Initialization
● 私有化建構函數,創立私有靜態變數,取得實例的 Func 靜態化,沒有多執行序問題
優點 | 缺點 |
---|---|
沒有加鎖,效率較高 | 類加載時初始化,稍微浪 |
class Hungry {
private static Hungry h = new Hungry();
private Hungry() {
}
public static Hungry getInstance() {
return h;
}
public static void say() {
System.out.println("Hello");
}
}
這種單例方式比較常用,類加載時就初始化,它是 基於 ClassLoader
機制 避免了多執行序同步問題(類加載時 static 就會被實例化)
● 但是初次呼叫時不一定是呼叫 getInstance 方法,由此可知 可能會造成在不必要創建時實例時被創建,可能會浪費資源
public class Test { public static void main(String[] args) { // 呼叫一個靜態方法,仍會創建 Hungry Field Hungry.say(); } }
--實作--
創建 20 個任務,確認沒有多執行序問題
Lazy Initialization
● Lazy Initialization
不初始化靜態變量,避免了不必要的資源浪費
這樣會有兩種狀況,執行序安全,執行序不安全
A. 執行序不安全
class LazyNoSyn {
private static LazyNoSyn l;
private LazyNoSyn() {
}
public static LazyNoSyn getInstance() {
if(l == null) {
l = new LazyNoSyn();
}
return l;
}
}
--實作--
創建 20 個任務,
執行序不安全
問題(看下圖左邊的藍標,明明是單例模式,但是它卻創建了多個物件);這個範例並非每次執行都會產生執行序多次創建物件的狀況(多執行序預設是沒有鎖的不安全操作,但是被非每次都會發生),你可以多按執行幾次嘗試看看
B. 執行序安全
重點多了 synchronized
關鍵字來保證多執行序訪問同段代碼的「同步」行為
class LazySyn {
private static LazySyn l;
private LazySyn() {
}
public static synchronized LazySyn getInstance() {
if(l == null) {
l = new LazySyn();
}
return l;
}
}
--實作--
同樣是創建 20 個任務但可以看到這次就是
執行序安全
(你可以多執行幾次)
優點 | 缺點 |
---|---|
延遲加載,在需要時才載入 | 類使用同步加鎖機制又會增加負載,每次呼叫 getInstance 都會進行一次同步動作,會造成不必要的開銷 |
雙鎖 DCL (Double Check Lock)
● 雙鎖機制是優化 Lazy Initialization
,保證執行序安全
class DCL {
private volatile static DCL d; // 4
private DCL() { }
public static DCL getInstance() {
if(d == null) { // 1
synchronized(DCL.class) { // 3
if(d == null) { // 2
d = new DCL();
}
}
}
return d;
}
}
A. 第一個 Check:避免不必要的 synchronized
同步,優化開銷
B. 第二次 Check:第一次加載才創建,第二次判斷 null 是為了避免並發問題 (可能有多個 thread 進入上一個判斷)
C. 區塊同步:使用 class 類同步 (Java 加載時 class 時會在 class 後創建一個不變的模板),或是使用一個靜態物件同步也可以
D. volatile
關鍵字:我們來看看 JVM 是如何創建一個物件實例的(假設類已經被 JVM 加載完畢)
new DCL()
看起來是一句程式,但實際上它並不是一個原子操作,它做了以下三件事
- 初始化記憶體:給 DCL 的實例分配記憶體
- 初始化物件:呼叫 DCL 的建構函數,初始化成員元素 (Field, Static Field)
- 賦予 Field:將 DCL 物件指向分配的記憶體空間 (給予它內存 Addr 此時 DCL's d Field 不再是 null)
Java 編譯器允許 CPU 失序執行,也就是上面執行的順序可能是
1-2-3
or1-3-2
,如果是1-3-2
這種狀況很難追蹤,已經分配記憶體空見卻未創建 !這時可以使用
volatile
關鍵字private volatile static DCL d;
volatile
也加減會影響效能(volatile 修飾語每次更改時都會即時存入記憶體
,在 Android 執行序會詳說)
--實作--
創建 20 個任務,
執行序安全
優點 | 缺點 |
---|---|
確保多執行序的單例化 | 第一次加載較慢,在高併發的環境下可能失敗(由於 JMM 模型) |
● JDK 1.5 以後才具體實現 volatile 關鍵
靜態內部類
● 在某些情況下 DCL 會失效(高並發時),Java 並發編程實踐
也有提及這種 DCL 最佳化是醜陋的,建議使用靜態內部類
class StaticInnerClass {
private StaticInnerClass() {
}
public static StaticInnerClass getInstance() {
return innerClass.sic;
}
private static class innerClass {
private static final StaticInnerClass sic = new StaticInnerClass();
}
}
這同餓漢式是使用 classLoader 加載機制,如果未使用內部類,那該 class 就不會被加載,它的 Field 也不會被創建,可以達到 延遲加載機制
--實作--
創建 20 個任務,
執行序安全
enum 列舉單例
● enum 類舉在 Java 中與普通類別是一樣的,可以有自己的方法,並在任何情況下都是單例
(透過反編譯 Class 可以看出 enum 成員各個都是一個列別)
public enum SingletonEnum {
INSTANCE;
public void toDo() {
System.out.println("to Do Something");
}
}
--實作--
● 可以透過 javac
自行編譯,在透過 javap
查看 enum 成員的實現方式,可以發現每個 Enum 成員都是 SingletonEnum 的實現物件 (static final)!
public enum SingletonEnum {
INSTANCE,
HELLO,
WORLD;
public void toDo() {
System.out.println("to Do Something");
}
}
單例:反序列化
● 注意,在一種情況下會出現重新建構物件的情況,就是反序列化,每次反序列化都會導致物件重新被創建
● 反序列化有一個特別的鉤子函數 readResolve
,只要寫這個方法並返回被實例化的物件就不會創建出新物件
private Object readResolve() throws ObjectStreamException {
turn ...;
● 以下為序列化 TestClass 與反序列化 TestClass
public class Main {
public static void main(String[] args) {
makeFile();
readFile();
}
private static void readFile() {
File in = new File("/home/alienpan/IdeaProjects/algorithm_ds/src/main/java/oop/singleton/serializable/SerialFile");
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(in))) {
System.out.println("readFile: " + objectInputStream.readObject().hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
private static void makeFile() {
File out = new File("/home/alienpan/IdeaProjects/algorithm_ds/src/main/java/oop/singleton/serializable/SerialFile");
if(out.exists() && out.delete()) {
System.out.println("Delete file");
}
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(out))) {
TestClass test = TestClass.getInstance();
objectOutputStream.writeObject(test);
objectOutputStream.flush();
System.out.println("makeFile: " + test.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
A. 首先測試尚未使用 readResolve
方法
public class TestClass implements Serializable {
private static final TestClass instance = new TestClass();
private TestClass() {
}
public static TestClass getInstance() {
return instance;
}
}
透過 hashCode
可以看到序列化、反序列化時兩者是 不同物件
目前使用靜態創建加載(Lazy Initialize),相同的你使用在 DCL 也一樣,序列化、反序列化時兩者是 不同物件
B. 使用 readResolve
方法,並返回相同物件
public class TestClass implements Serializable {
private static final TestClass instance = new TestClass();
private TestClass() {
}
public static TestClass getInstance() {
return instance;
}
@Serial
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
透過 hashCode
可以看到序列化、反序列化時兩者是 相同物件
● enum 宣告則很特別,就算不用添加
readResolve
方法一樣是可以達到 序列化、反序列化時兩者是 同物件
public enum TestClass {
INSTANCE;
public static TestClass getInstance() {
return INSTANCE;
}
}
Android Source Content Service
以下是一段很常見到的 ListView 加載方式,並使用了簡單的 set/getTag 去緩存資源來達到快速加載,這個優化並不是當前文章的主要內容,所以不去細說
取得 LayoutInflater
● 最常使用在 ListView 的 BaseAdapter 做出 Layout 界面加載,這次主要關注在加載圖片時的 LayoutInflater.from(Context) 方法
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = findViewById(R.id.listView);
MyBaseAdapter adapter = new MyBaseAdapter(new String[] {"Apple", "Box", "Car", "Drop"}, this);
listView.setAdapter(adapter);
}
}
class MyBaseAdapter extends BaseAdapter {
private String[] strings;
private Context context;
MyBaseAdapter(String[] strings, Context context) {
this.strings = strings;
this.context = context;
}
@Override
public int getCount() {
return strings.length;
}
@Override
public Object getItem(int position) {
return strings[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null) {
// @ 分析 LayoutInflater.from 方法
convertView = LayoutInflater.from(context).inflate(R.layout.my_item, null);
holder = new ViewHolder();
holder.textView = convertView.findViewById(R.id.MyTitle);
holder.imageView = convertView.findViewById(R.id.MyImage);
// 設定緩存儲存,設定一個 Tag 方便下次取出 ViewHolder
convertView.setTag(holder);
} else {
// 使用之前設定的 Tag 取出已有的 ViewHolder
holder = (ViewHolder) convertView.getTag();
}
holder.textView.setText(strings[position]);
return convertView;
}
private static class ViewHolder {
ImageView imageView;
TextView textView;
}
}
● LayoutInflater 的靜態 from 方法:它內部使用 context 抽象方法 getSystemService (Context 本身也是抽象類)
// LayoutInflater.java
public static LayoutInflater from(Context context) {
// 內部透過 Context#getSystemService 方法取得 LayoutInflater
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
// --------------------------------------------------------------------
// Context.java
public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
Context 包裝 Activity
● 這裡要從 App 程式的 main 入口開始分析,分析的過程可以得到 Context 是如何去註冊服務的
類 | 關注功能 |
---|---|
ActivityThread.java | 內部有一個 main 方法,是 Java 程式的起點方法 |
ApplicationThread.java | 屬於 ActivityThread 的內部類,是該進程與外部進程通訊的橋樑 |
// ActivityThread.class
public class ActivityThread {
final ApplicationThread mAppThread = new ApplicationThread();
...
// 目前 system 是 false
private void attach(boolean system, long startSeq) {
// sCurrentActivityThread 使用 volatile 修飾
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
// 取得與 Binder 通訊的代理
// 這裡由 AIDL 實現
final IActivityManager mgr = ActivityManager.getService();
try {
// @ 追蹤 attachApplication
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
...
} else {
... SystemSerivce 才會走到這
}
...
}
public static void main(String[] args) {
...
// "1. "
// 準備主執行序的 Looper (建構)
Looper.prepareMainLooper();
// 創建 ActivityThread 物件
ActivityThread thread = new ActivityThread();
// 非系統載入
thread.attach(false);
if (sMainThreadHandler == null) {
// 取得該 class 的 Handler
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop(); // 啟動 Looper
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
A. 這段程式可以看出 Android 的消息驅動是透過 Looper 推動 (要先了解 Handler 機制)
B. IActivityManager#attachApplication
會觸及 Binder 機制,這裡我們當作一個黑盒子 (略過與 AMS 進程通訊),而 attachApplication
函數最終會回到該進程中的 ActivityThread#handleLaunchActivity
方法
// ActivityThread.class
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
... 省略部份
// @ 追蹤 performLaunchActivity
final Activity a = performLaunchActivity(r, customIntent);
...
}
● ActivityThreadperformLaunchActivity:準備啟動 Activity
A. 反射創建 Activity、對應的 Context 類 (Context 實做類是 ContextImpl 物件)
// ActivityThread.class
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
... 省略部份
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 透過 Instrumentation 創建 Actiivty
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
} /* 省略 catch */
...
}
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
...
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
... 省略部份
return appContext;
}
// ----------------------------------------------------------------
// ContextImpl.java
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
...
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, ContextParams.EMPTY,
attributionTag, null, activityInfo.splitName, activityToken, null, 0, classLoader,
null);
...
return context;
}
B. 創建 Application 類,並讓 Context 將 Activity 包裝起來,之後在呼叫 Activity#attach 方法
// ActivityThread.class
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
... 省略部份
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
...
// Context 內包裝 Activity
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken, r.shareableActivityToken);
...
// 透過 Instrumentation 呼叫 onCreate 方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
}
} /* 省略 catch */
...
}
● 從這裡我們可以知道
A. Context 的實做類是
ContextImpl
(接下來我們會繼續分析)B. Activity 的生命週期大部分可以透過 Instrumentation Hook 改寫,因為 Activity 的控制大分份也都通過 Instrumentation
ContextImpl 分析:SystemServiceRegistry 單例創建 PhoneLayoutInflater
● 我們了解到 Activity 是由 Context 來包裝,而 Context 的實做類是 ContextImpl 類,現在我們來看看 ContextImpl 類
A. 複習回憶:現在的目的是 了解 LayoutInflater 這個服務 註冊時機
// LayoutInflater.java
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
// --------------------------------------------------------------------
// ContextImpl.java
@Override
public Object getSystemService(String name) {
// @ 分析 SystemServiceRegistry 物件
return SystemServiceRegistry.getSystemService(this, name);
}
B. 分析 SystemServiceRegistry:SystemServiceRegistry 類是一個 單例物件(符合當前主題)
● 在 SystemServiceRegistry 類加載時會先加載 static Field,包含 static 區塊
● 每個 registerService 實做 ServiceFetcher#createService 方法,也就是說 LayoutInflater 的實現物件是 PhoneLayoutInflater 類
// SystemServiceRegistry.java
// Activity 可以透過 Context#getSystemService 獲取服務
final class SystemServiceRegistry {
private SystemServiceRegistry() { } // 私有化 - 建構函數
static {
... 省略其他服務
// @ 追蹤 registerService
registerService(
// 服務名
Context.LAYOUT_INFLATER_SERVICE,
// Class 類
LayoutInflater.class,
// @ 追蹤 CachedServiceFetcher
new CachedServiceFetcher<LayoutInflater>() {
// 實做 createService 方法
@Override
public LayoutInflater createService(ContextImpl ctx) {
// 服務真正實現類
return new PhoneLayoutInflater(ctx.getOuterContext());
}}
);
}
// 儲存在兩個 Map 中,這個函數只能使用在初始化
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
// 各種 Map 緩存
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
// 服務 Map
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
}
// ContextImpl 呼叫
// 透過緩存 SYSTEM_SERVICE_FETCHERS 找到對應的服務
public static Object getSystemService(ContextImpl ctx, String name) {
if (name == null) {
return null;
}
final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
if (fetcher == null) {
... 無法取得對應緩存
return null;
}
// 呼叫實做 ServiceFetcher 的類 getService 方法
final Object ret = fetcher.getService(ctx);
...
return ret;
}
}
C. CachedServiceFetcher:功能有兩個
● 懶加載 Service 物件,在需要時才會呼叫 createService 方法創建
● 多 Thread 要求 Service 時會讓第一個 thread 創建,其他 thread 休眠(wait)
// SystemServiceRegistry.java
// Activity 可以透過 Context#getSystemService 獲取服務
final class SystemServiceRegistry {
private static int sServiceCacheSize;
// 匿名靜態內部類
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
// 緩存的 index
private final int mCacheIndex;
public CachedServiceFetcher() {
// 屬性 sServiceCacheSize 是靜態變數
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
final int[] gates = ctx.mServiceInitializationStateArray;
boolean interrupted = false;
T ret = null;
for (;;) {
boolean doInitialize = false;
// 同步鎖
synchronized (cache) {
// ContextImpl 內有緩存就直接返回
T service = (T) cache[mCacheIndex];
if (service != null) {
ret = service;
break; // exit the for (;;)
}
// 如果是 `STATE_READY` or `STATE_NOT_FOUND`
// 則定義 ContextImpl 中 Service 尚未初始化
if (gates[mCacheIndex] == ContextImpl.STATE_READY
|| gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED;
}
// 如果有多的 thread 到這一步,必須確保第一個 Thread 呼叫 createService()
// 所以必須定義 STATE_UNINITIALIZED 為 STATE_INITIALIZING 狀態
if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) {
doInitialize = true;
gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING;
}
}
// 只有第一個 thread 才會執行以下 if 條件
if (doInitialize) {
T service = null;
@ServiceInitializationState
int newState = ContextImpl.STATE_NOT_FOUND;
try {
// 呼叫 createService 方法(每個 Service 實做類)
service = createService(ctx);
newState = ContextImpl.STATE_READY;
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
} finally {
// 最終會呼叫所有等待的 thread 醒來
synchronized (cache) {
cache[mCacheIndex] = service;
gates[mCacheIndex] = newState;
cache.notifyAll();
}
}
ret = service;
break; // exit the for (;;)
}
synchronized (cache) {
// 所有不是第一個 thread 都會進入這
while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
try {
// Clear the interrupt state.
interrupted |= Thread.interrupted();
cache.wait();
} catch (InterruptedException e) {
// Thread 被中斷
interrupted = true;
}
}
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
return ret;
}
public abstract T createService(ContextImpl ctx);
}
}
● 在這其中也可以發現 依賴倒置的關係,高層模(SystemServiceRegistry)組依賴抽象(ServiceFetcher),而不是細節(CachedServiceFetcher、StaticServiceFetcher)
ContextImpl 與 SystemServiceRegistry 關係
● ContextImpl 本身並不是單例(可以有多個),但是其服務就是單例,呼應上面所 LayoutInflater.from()
所呼叫的函數,Context.getSystemService 其 context 透過上面的追蹤可以知道實做類就是 ContextImpl 類
● 從這邊我們可以看到 SystemSerivceRegistry 的單例是使用 static 靜態區塊加載 (Eager Initialization, 也稱為餓漢式),代表所有的服務在 SystemSerivceRegistry 這個類加載的時候就已經註冊完畢
雖說是靜態加載,但是也只加載了服務的數量,並未真正創建,所以應該是說 懶加載 + 延遲加載
靜態加載 -> 定義出總數量,以及其 index 位置
延遲加載 -> 真正的服務物件
● 特別說說 ContextImpl 中的 getSystemService
函數,它把 this 作為參數傳入,並且可以看到
A. 一個 Activity 有一個 ContextImpl 物件
B. 在 SystemServiceRegistry.getSystemService 中透過傳入的 Context 物件取得服務的物件,若沒有該物件則使用當前 context 創建物件
結論: 可以透過當前的 Context 創建服務,若尚未使用到該服務則不加載
● 這裡的服務並非進程間的服務(並非 AMS、WMS、PKMS... 等等)
更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
創建模式:Creation Patterns
● 創建模式 PK
● 創建模式 - Creation Patterns
:
創建模式用於「物件的創建」,它關注於如何更靈活、更有效地創建物件。這些模式可以隱藏創建物件的細節,並提供創建物件的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結
● Singleton 單例模式 | 解說實現 | Android Framework Context Service
● Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer
● Factory 工廠方法模式 | 解說實現 | Java 集合設計
● Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗
● Clone 原型模式 | 解說實現 | Android Framework Intent
行為模式:Behavioral Patterns
● 行為模式 PK
● 行為模式 - Behavioral Patterns
:
行為模式關注物件之間的「通信」和「職責分配」。它們描述了一系列物件如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結
● Stragety 策略模式 | 解說實現 | Android Framework 動畫
● Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService
● Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞
● Specification 規格模式 | 解說實現 | Query 語句實做
● Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計
● Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存
● Visitor 設計模式 | 實現與解說 | 物件導向設計
● Template 設計模式 | 實現與解說 | 物件導向設計
● Mediator 模式設計 | 實現與解說 | 物件導向設計
● Composite 組合模式 | 實現與解說 | 物件導向設計
● Observer 觀察者模式 | JDK Observer | Android Framework Listview
結構模式:Structural Patterns
● 結構模式 PK
● 結構模式 - Structural Patterns
:
結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結
● Decorate 裝飾模式 | 解說實現 | 物件導向設計
● Iterator 迭代設計 | 解說實現 | 物件導向設計