Factory 工廠方法模式 | 解說實現 | Java 集合設計

Factory 工廠方法模式 | 解說實現 | Java 集合設計

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、abstractFactory定義要產生的目標,但抽象又依賴於抽象 (依賴倒置)
-ConcreateFactory具體實現工廠
interface、abstractProduct定義抽象產品,是 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 集合設計

ListSet 來說都繼承於 Collection 接口,而 Collection 繼承 Iterable 接口,Iterable 讓使用者一定要返回一個迭代類

代表對象
FactoryIterable
ProductIterator,它代表了 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 分析 篇章


更多的物件導向設計

物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!

設計建模 2 大概念- UML 分類、使用

物件導向設計原則 – 6 大原則(一)

物件導向設計原則 – 6 大原則(二)

創建、行為、結構型設計 8 個比較 | 包裝模式 | 最佳實踐

創建模式 Creation Patterns

創建模式 PK

創建模式 - Creation Patterns

結構模式 Structural Patterns

結構模式 PK

結構模式 - Structural Patterns

結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結

Bridge 橋接模式 | 解說實現 | 物件導向設計

Decorate 裝飾模式 | 解說實現 | 物件導向設計

Proxy 代理模式 | 解說實現 | 分析動態代理

Iterator 迭代設計 | 解說實現 | 物件導向設計

Facade 外觀、門面模式 | 解說實現 | 物件導向設計

Adapter 設計模式 | 解說實現 | 物件導向設計

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響