Overview of Content
如有引用參考請詳註出處,感謝
本文將深入探討 Factory
(工廠方法)設計模式在 Android框架
中的應用與實現。
首先,將介紹 Factory
模式的使用場景,包括詳細的 UML 示意圖和定義,幫助讀者理解其結構和運作方式,接著,將探討Factory模式的特點,以便讀者更好地理解其優勢與適用情境。
在進入具體實現之前,將詳細討論 多種 Factory 模式的變形,包括使用反射、靜態工廠、多工廠…等不同實現方式,以及如何在Android框架中應用這些變形。隨後,將介紹Java中常見的集合類型 ArrayList 和 HashSet 的實現跟 Factory 設計之間的關係
最後在 Android 源碼中 ActivityThread 如何調用 onCreate 方法,以及如何加載XML布局。通過這些內容,讀者將能夠深入了解 Factory模式在Android開發中的應用場景及其相關實現技巧。(這個部份會引用的我的其它篇文張… 避免該篇文章過長)
Factory 使用場景
需要特別處的初始化物件的地方 (複雜物件),都可以使用,用 new 就可以產生物件的地方則不需要特別使用
把生產過程掩飾在工廠中,使用者不需要知道詳細過程 (隱藏細節)
A. 替代 new
關鍵字(雖說該模式可以替代 new
關鍵字,但在設計時仍請注意複雜度,否則會造成設計過度,失去應有的可維護性)
B. 靈活、可拓展性的框架
C. 鬆耦合:工廠模式配合介面導向編程,可以達到讓使用者依賴工廠產生的物件,而不必自己創建物件
在目標物件、使用者之間創建一個工廠,由工廠來創建、監控物件的生產(隱藏細節)
D. 定義一個用於建立物件的介面,讓子類別決定實例化哪個類別,讓子類返回實例類別讓使用者使用
E. 單元測試:解耦不同類之間的相互依賴關係(由於測試框架的發展,這方面使用已經弱化)
● 通常可以用來封裝物件建立的細節 並管理、控制實體(instance)的數量
也就是說有類似 Pool 的功能
● 工廠設計 vs. 單例設計
● 工廠設計優點:
可以產出多個產品並進行控管,並且可以依照需要創建不同產品名,有較高的函數可讀性
單例設計 大多會依照
getInstance
取名,可讀性較差● 相似之處:
它們都是為了創建物件,所以工廠模式中可以用到與單例設計類似的技法
像是
lazy singleton
,enum object
... 等等
Factory UML & Factory 定義
● Factory 定義:
一個用於創建對象的 interface(Factory),讓子類(ConcreateFactory)來決定要實現哪個類(ConcreateProduct)
工廠方法使一個類的實例化時機轉移到子類
這時要注意,父類、子類實例化的時機點 (虛擬機會先建構父類,再建構子類),若父類調用子類的時機點不對則會發生 Crash
● Factory UML 關係
描述 | 類 | 負責範圍 |
---|---|---|
interface、abstract | Factory | 定義要產生的目標,但抽象又依賴於抽象 (依賴倒置) |
- | ConcreateFactory | 具體實現工廠 |
interface、abstract | Product | 定義抽象產品,是 Factory 要產生的目標 |
- | ConcreateProduct | 具體實現產品 |
Factory 特點
● 工廠模式是 高內聚 的一種表現
● 隱藏了創建對象的細節;使用者不用負擔細節,過多的細節增加了使用的成本
● 同時由於工廠並非直接依賴實體的產品類(Product
),而是依賴抽象產品(典型的解耦框架),所以有優秀的拓展性
● 工廠模式是一個很好的模式,但是 在多出別的類別時,要引入抽象層(抽象化 Factory
, Product
),會導致程式變複雜,如果是一個簡單的情況就要考量是否使用了
在有可能拓展不同創建類別的情況下再使用會比較好
Factory 實現
Factory 標準
A. Product
:ITravelInfo 定義了一個抽象產品,它會與 Factory 產生關係
public interface ITravelInfo {
String getCityName();
String getTravelInfo();
}
B. ConcreateProduct
:定義產品實現的細節,透過 Factory 不同的組裝,設定不同的數值
public class TravelInfoImpl implements ITravelInfo {
public String name;
public boolean chineseLang;
public List places = new ArrayList<>();
public TravelInfoImpl(String name, boolean chineseLang) {
this.name = name;
this.chineseLang = chineseLang;
}
@Override
public String getCityName() {
return name;
}
@Override
public String getTravelInfo() {
return places.toString() + ", Chinese lang: " + (chineseLang ? "Yes" : "No");
}
}
C. Factory
:ICity 定義工廠的核心,它依賴於 ITravelInfo 接口,也就是 Factory 要產生的不同的 ITravelInfo 類
public interface ICity {
ITravelInfo getTaiwan();
ITravelInfo getJapan();
}
D. ConcreateFacotry
:實做 ICity 工廠細節,在這裡加入(隱藏)一些細節,避免讓使用者知道,當然也可以減輕使用者使用的負擔
public class CityFactory implements ICity {
@Override
public ITravelInfo getTaiwan() {
TravelInfoImpl travelInfo = new TravelInfoImpl("Taiwan", true);
travelInfo.places.add("taipei");
return travelInfo;
}
@Override
public ITravelInfo getJapan() {
TravelInfoImpl travelInfo = new TravelInfoImpl("Japan", false);
travelInfo.places.add("tokyo");
return travelInfo;
}
}
● 使用 Factory 類:
public class FactoryMain {
public static void main(String[] args) {
ICity iCity = new CityFactory();
ITravelInfo taiwan = iCity.getTaiwan();
print(taiwan);
ITravelInfo japan = iCity.getJapan();
print(japan);
}
public static void print(ITravelInfo travelInfo) {
System.out.println("City: " + travelInfo.getCityName() +
", place: " + travelInfo.getTravelInfo());
}
}
● 符合依賴倒置 (依賴高層模組,而不是低層模組)
Factory 變形:反射
● 從上面的簡單範例可以看出,決定產出了類別由工廠決定,所以每次都要修改 工廠(ConcreateFactory)內容才能產出不同類的實例,使用起來很不方便
你可能要新增一個 City 就需要修改 ICity 接口,這樣不符合開閉原則
A. IFactory
修改:ICity 的抽象定義,讓多接收一個 Class 參數(並用泛型限制參數)
public interface ICity {
ITravelInfo getTravel(Class<? extends ITravelInfo> clz);
}
B. ConcreateProduct
修改:為每個 Product 類創建單獨的實做類,並將細節放在實做類上
public class JapanTravelInfo implements ITravelInfo {
public String name;
public boolean chineseLang;
public List<String> places = new ArrayList<>();
public JapanTravelInfo() {
this.name = "Japan";
this.chineseLang = false;
places.add("Tokyo");
}
@Override
public String getCityName() {
return name;
}
@Override
public String getTravelInfo() {
return places.toString() + ", Chinese lang: " + (chineseLang ? "Yes" : "No");
}
}
public class TaiwanTravelInfo implements ITravelInfo {
public String name;
public boolean chineseLang;
public List<String> places = new ArrayList<>();
public TaiwanTravelInfo() {
this.name = "Taiwan";
this.chineseLang = true;
places.add("Taipei");
}
@Override
public String getCityName() {
return name;
}
@Override
public String getTravelInfo() {
return places.toString() + ", Chinese lang: " + (chineseLang ? "Yes" : "No");
}
}
C. ConcreateFactory
修改:利用 class 反射動態創建不同的類
public class CityFactory implements ICity {
@Override
public ITravelInfo getTravel(Class<? extends ITravelInfo> clz, String name) {
try {
Constructor<? extends ITravelInfo> constructor = clz.getConstructor();
return constructor.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
● 最後修改一下 User 使用的方式即可
public class FactoryMain {
public static void main(String[] args) {
ICity iCity = new CityFactory();
ITravelInfo taiwan = iCity.getTravel(TaiwanTravelInfo.class);
print(taiwan);
ITravelInfo japan = iCity.getTravel(JapanTravelInfo.class);
print(japan);
}
public static void print(ITravelInfo travelInfo) {
System.out.println("City: " + travelInfo.getCityName() +
", place: " + travelInfo.getTravelInfo());
}
}
● 使用反射的話,使用者就必須知道他自己目標要產生的類,這就不符合迪米特原則(最少知識原則),讓我們來看看 UML 圖
單一工廠:靜態工廠
● 靜態工廠又稱為簡單工廠,它 把原本抽象的 Factory 簡化為實體類,如下
A. 私有化建構函數
B. 建立可被靜態呼叫並創建物件的函數
class CityFactory private constructor() {
companion object {
fun getTravel(clz: Class<out ITravelInfo>): ITravelInfo {
return clz.getConstructor().newInstance()
}
}
}
● 使用:高層模組在呼叫時就改成使用靜態類
fun main() {
var info = CityFactory.getTravel(TaiwanTravelInfo::class.java)
println("${info.cityName} - ${info.travelInfo}")
info = CityFactory.getTravel(JapanTravelInfo::class.java)
println("${info.cityName} - ${info.travelInfo}")
}
靜態工廠十分好用,但它的缺點也很明顯,就是 Factory 無法拓展,不符合依賴倒置的原則
多工廠:多產品
● 有時候我們在創建(實例化)一個產品時所需要耗費的工相當的多(或是說攏長),把產品的實例化全部寫在一個類中實現實,就會顯的相當難看,這時就可以做出比較針對性的區分
A. Factory
:抽象工廠只有一個方法,讓 Concrete Factory 透過參數,或是其他方式自己決定要生產的實體
interface ICity {
// 假設之前有很多個方法,現在全部歸納為一個方法
fun getTravel() : ITravelInfo
}
B. ConcreteFactory
:Concrete Factory 與 Concrete Product 對應創建一個實體類,有多少 Product 就有多少 Factory
class TaiwanFactory : ICity {
override fun getTravel(): ITravelInfo {
return TaiwanTravelInfo()
}
}
class JapanFactory : ICity {
override fun getTravel(): ITravelInfo {
return JapanTravelInfo()
}
}
● 從這裡也可以很清楚的看到一個問題,就是類的拓增導致維護出現難度,並要同時考慮 Factory、Product 的關係
解決方法:可以透過一個協調類來讓 Concrete Factory 與 Concrete Product 不要直接調用到對方,降低其耦合
反射工廠:單例工廠
● 可以統一使用反射的方式來取得私有建構函數並創建唯一物件,並將這個唯一物件透過工廠包裝(工廠可以不用是單例),如下範例
A. 創建一個私有類,它代表了一個將要被單例化的對象
class Travel private constructor() {
fun todo() {
// do something
}
}
B. 產品抽象、具體產品:單例產品
interface ITravel {
fun place(): String
}
// 私有化建構函數
class Travel private constructor(): ITravel {
override fun place(): String {
return "Taiwan"
}
}
C. 抽象、具體工廠類:透過 反射(創建對象) + 靜態(單例) 的方式達成與單例相同的效果
interface ICityFactory {
val travel : ITravel
}
class SingleFactory: ICityFactory {
companion object {
// 靜態 產品
private val _travel: ITravel
get() {
return Travel::class.java.getConstructor().newInstance()
}
}
override val travel: ITravel
get() = _travel
}
這裡必須要使用靜態儲存產品才能達到單例的效果
● 這 必須要團隊成員共同遵守這個規範,否則仍可能會創建多個對象(別人也可以用反射自己稿一個新對象出來)
拓展工廠:覆用工廠
● 在需要時才加載產品,並且加載後將產品緩存,在需要時才取用;以下創建一個類似將產品單例緩存、並懶加載的覆用工廠
之所以是單例,是因為 Cache 是靜態的 Field
class ProductFactory {
companion object {
private val cacheMap = mutableMapOf<Class<*>, ITravel>()
fun createProduct(productClz : Class<*>) : ITravel {
cacheMap[productClz].let {
if (it != null) {
return it
}
val travel = if (productClz == TaiwanTravel::class.java) {
TaiwanTravel()
} else {
JapanTravel()
}
cacheMap[productClz] = travel
}
return cacheMap.getValue(productClz)
}
}
}
Java 集合設計
以 List、Set 來說都繼承於 Collection 接口,而 Collection 繼承 Iterable 接口,Iterable 讓使用者一定要返回一個迭代類
代表 | 對象 |
---|---|
Factory | Iterable |
Product | Iterator,它代表了 Product |
// Iterable.java
public interface Iterable<T> { // Iterable 接相當於一個抽象工廠
// 必須實做一個迭代類
Iterator<T> iterator();
... 省略 default 方法
}
// --------------------------------------------------------------
// Iterator.java
public interface Iterator<E> { // Iterator 是一個工廠生產的抽象產品 (IProduct)
boolean hasNext();
E next();
... 省略 default 方法
}
ArrayList
● Factory:ArrayList 是我們常使用到的數據結構實現類,它內部就有實現 iterator
方法,它必須實做了工廠 Iterable 接口
// ArrayList.java
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
{
... 省略其他方法
public Iterator iterator() {
return new Itr(); // 工廠類,創建一個 Itr 類
}
}
● Product:Itr 類負責產生迭代 ArrayList 的對象
// ArrayList.java
private class Itr implements Iterator<E> {
protected int limit = ArrayList.this.size;
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor < limit;
}
public E next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int i = cursor;
if (i >= limit)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
... 省略部份方法
}
● ArrayList UML
HashSet
Set 接口實現的子類特點是,禁止內部成員的重複
● Factory:HashSet 內部就有實現 iterator
方法,它必須實做了工廠 Iterable 接口
// HashSet.java
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
// 題外 transient 是禁止反序列化的標誌
private transient java.util.HashMap<E, java.lang.Object> map;
public Iterator<E> iterator() {
return map.keySet().iterator();
}
}
● Product:HashSet#iterator 迭代是返回 HashMap#keySet#iterator 方法,從這裡可以看出來 iterator 針對不同的結構,由實做類返回不同的處理對象
所以每一個數據結構都會實現各自的
iterator
(抽象產品)
// HashMap.java
public class HashMap<K, V> extends java.util.AbstractMap<K, V>
implements java.util.Map<K, V>, java.lang.Cloneable, java.io.Serializable {
final class KeySet extends AbstractSet<K> {
public final Iterator<K> iterator() { return new KeyIterator(); }
... 省略部份方法
}
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
public final boolean hasNext() {
return next != null;
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
}
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
... 省略部份
}
}
● HashSet UML
Android source GUI
在 Activity#onCreate 時我們會用 setContent 方法來繪製布局,給予不同 xml 檔案就會設置不同布局,這就是一個工廠模式的實做 (就像是反射工廠,給予不同 class 最終返回不同類)
ActivityThread 呼叫 onCreate
● 請看 ActivityThread 分析 篇章
加載 XML 布局
● 請看 LayoutInflator 分析 篇章
更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
創建模式 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 迭代設計 | 解說實現 | 物件導向設計