Android建立型設計模式(簡單工廠,工廠方法,抽象工廠,BitmapFactory簡單工廠分析,Retrofit抽象工廠分析)

語言: CN / TW / HK

[TOC]

建立型設計模式(簡單工廠,工廠方法,抽象工廠)

工廠模式主要是用於對物件例項化的一種管理模式,一般情況下,我們都需要使用new關鍵字來建立一個物件,那麼我們就需要用工廠模式來統一管理我們物件的建立過程,把物件的建立交給該模式去處理,這樣我們就不用手動的去new物件了,工廠模式主要是將建立物件的具體過程遮蔽隔離起來。

首先我們先來看一個Android裡面開發常用的例子SharedPreferences。這是佈局檔案

```xml

<TextView
    android:id="@+id/infoTv"
    android:onClick="getInfo"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

```

我們一般都這麼寫,常規寫法

```java public class MainActivity extends AppCompatActivity { TextView textView; private SharedPreferences preferences;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    textView = findViewById(R.id.infoTv);
    //模擬用SP存入資料
    SPUtils.getInstance().putString("username", "Colin").putString("userAge", "33").commit();
}
//這裡是模擬從SP取出我們想要的資料展示在UI上
public void getInfo(View view) {
    String username = SPUtils.getInstance().getString("username", "");
    String userAge = SPUtils.getInstance().getString("userAge", "");
    textView.setText("username=" + username + "userAge=" + userAge);
}

} ```

SP工具類

```java public class SPUtils { private SharedPreferences.Editor editor; private SharedPreferences preferences; private volatile static SPUtils mInstance;

private SPUtils() {

}

public static SPUtils getInstance() {
    if (mInstance == null) {
        synchronized (SPUtils.class) {
            if (mInstance == null) {
                mInstance = new SPUtils();
            }
        }
    }
    return mInstance;
}

public void init(Context context) {
    preferences = context.getSharedPreferences("Cache", Context.MODE_PRIVATE);
    editor = preferences.edit();
}

public SPUtils putString(String key, String value) {
    editor.putString(key, value);
    return this;
}

public void commit() {
    editor.commit();
}

public String getString(String key, String defaultStr) {
    return preferences.getString(key, defaultStr);
}

} ```

初始化

java public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); SPUtils.getInstance().init(this); } }

小結:

優點:通過SPUtils工具類來優化程式碼,就不用每次都在Activity裡面重複寫SP的程式碼了,做到了統一管理。

缺點:不能靈活改變獲取資料的方式。資料如果更換成記憶體,或者資料庫,MMKV的方式存取,那麼就要將上面用到SP的地方都換掉,換成記憶體換成,資料庫的方式儲存的話,那麼我們需要改動的程式碼就很多。

一.簡單工廠模式

其實我覺得簡單工廠模式是一種書寫的思維習慣,不太像真實的軟體設計模式。

由上面的例子引出下面這幅圖。假設我們有4中方式操作資料,那麼我們複寫4份不同的程式碼,那就很無語了;那麼我們可以定義一套規則,有寫入資料,取出資料的操作,然後再交給不同方式的不同物件去處理就好。

```java

/* 定義好寫入資料和讀取資料的介面,類似於一種規則, 都是我們常見的資料型別, / public interface IOHandler { //寫資料 void put(String key, String value);

void put(String key, int value);

void put(String key, long value);

void put(String key, boolean value);

void put(String key, Object value);

void put(String key, double value);

void put(String key, float value);

//讀取資料

String getString(String key);

Double getDouble(String key);

boolean getBoolean(String key);

float getFloat(String key);

int getInt(String key);

Object getObject(String key);

long getLong(String key);

} ```

我們這裡搞一個SP的實現類,實現上面我們寫的介面

```java / 這個類的含義是通過SP的方式對資料進行讀取 / public class SPIOHandler implements IOHandler { @Override public void put(String key, String value) { //寫資料,通過上面我們封裝好的SPUtils工具類,相當於又加了一層封裝 //到時候我們直接呼叫SPIOHandler的put方法就行,其他都不用管了 SPUtils.getInstance().putString(key, value).commit(); }

@Override
public void put(String key, int value) {

}

@Override
public void put(String key, long value) {

}

@Override
public void put(String key, boolean value) {

}

@Override
public void put(String key, Object value) {

}

@Override
public void put(String key, double value) {

}

@Override
public void put(String key, float value) {

}

@Override
public String getString(String key) {
    //獲取資料也是一樣
    return SPUtils.getInstance().getString(key, "");
}

@Override
public Double getDouble(String key) {
    return null;
}

@Override
public boolean getBoolean(String key) {
    return false;
}

@Override
public float getFloat(String key) {
    return 0;
}

@Override
public int getInt(String key) {
    return 0;
}

@Override
public Object getObject(String key) {
    return null;
}

@Override
public long getLong(String key) {
    return 0;
}

} ```

這裡我們模擬兩種方式對資料的存取,一個是SP,一個是下面這個記憶體的形式。

```java /* * IOHandler的實現類,利用記憶體的方式對資料進行寫入和讀取 / public class MemoryIOHandler implements IOHandler { //這裡是一個快取物件,可以對圖片資料進行快取,下次我們取圖片的時候就不用再去請求網路了 //可以直接從快取拿 LruCache mCache = new LruCache<>(10 * 1024 * 1024);

@Override
public void put(String key, String value) {
    mCache.put(key, value);
}

@Override
public void put(String key, int value) {
    mCache.put(key, value);
}

@Override
public void put(String key, long value) {
    mCache.put(key, value);
}

@Override
public void put(String key, boolean value) {
    mCache.put(key, value);
}

@Override
public void put(String key, Object value) {
    mCache.put(key, value);
}

@Override
public void put(String key, double value) {
    mCache.put(key, value);
}

@Override
public void put(String key, float value) {
    mCache.put(key, value);
}

@Override
public String getString(String key) {
    return (String) mCache.get(key);
}

@Override
public Double getDouble(String key) {
    return (Double) mCache.get(key);
}

@Override
public boolean getBoolean(String key) {
    return (boolean) mCache.get(key);
}

@Override
public float getFloat(String key) {
    return 0;
}

@Override
public int getInt(String key) {
    return 0;
}

@Override
public Object getObject(String key) {
    return null;
}

@Override
public long getLong(String key) {
    return 0;
}

} ```

在一開始我們是這樣寫入資料的。(寫入資料舉例,讀取資料亦是如此)

java SPUtils.getInstance().putString("username", "Colin").putString("userAge", "33").commit();

現在我們可以改成這樣,通過我們封裝的介面引用指向對應實現的子類物件

java private IOHandler ioHandler; ioHandler = new SPIOHandler(); ioHandler.put("username", "Colin"); ioHandler.put("userAge", "33");

我們這裡切換也是非常方便的,前面說的我們獲取資料也有可能用其他方法獲取,比如上面說的記憶體方式,此時我們可以直接切換實現類即可。

java ioHandler = new MemoryIOHandler(); ioHandler.put("username", "Colin"); ioHandler.put("userAge", "33");

這種寫法:

優點:用不同的方式去寫入讀取資料都不需要關注它的具體實現,我們只需要切換對應的實現類就可以實現同樣的邏輯。

缺點:在Activity上需要使用到這個類的時候都要手動去new物件。

引出簡單工廠模式

從上面的程式碼可以看出,我們的UI介面每次都要在用到的地方new物件,對建立物件的依賴性太強,為了減少Activity對建立物件的依賴,我們定義一個工廠類,我們需要什麼物件就傳入對應的型別匹配即可。

```java /* * IOHandler工廠,幫助我們建立具體的實現了IOHandler的類 / public class IOHandlerFactory {

public enum IOType {
    PREFERENCES, MEMORY
}
//建立具體的IOHandler子類物件
public static IOHandler createIOHandle(IOType ioType) {
    IOHandler ioHandler = null;
    switch (ioType) {
        case PREFERENCES:
            ioHandler = new SPIOHandler();
            break;
        case MEMORY:
            ioHandler = new MemoryIOHandler();
            break;
    }
    return ioHandler;
}

} ```

來看下我們呼叫:

跟我們上面的其實大同小異,都是獲取對應的物件,但是這裡的優點是隱藏了物件的建立,解除了Activity和new例項的耦合,把建立物件的動作交給了我們定義好的工廠類,這就是簡單工廠模式。

java ioHandler = IOHandlerFactory.createIOHandle(IOHandlerFactory.IOType.PREFERENCES); ioHandler.put("username", "Colin"); ioHandler.put("userAge", "33");

簡單總結

優點:我們可以對建立的物件進行一些 “加工” ,而且呼叫方並不知道,因為工廠隱藏了這些細節,沒有工廠的話,那我們就得自己在UI上寫這些程式碼

缺點:每次增加子類或者刪除子類物件的建立都需要開啟這簡單工廠類來進行修改,違反了我們的開閉原則,我們儘量不要修改已封裝好的基類,簡單來說就是不方便程式碼擴充套件,要修改的地方太多。

二.工廠方法模式

根據上面的前提我們優化一下程式碼,儘量保證面向介面程式設計。工廠方法模式是對簡單工廠模式進一步的解耦,在工廠方法模式中是一個子類對應一個工廠類,而這些工廠類都實現於一個抽象介面IOFactory,這樣就避免了簡單工廠模式,程式碼都在一個工廠類裡面,拆分成了一個個的工廠小類,用到哪個類就使用它對應的工廠類就行。

java //定義工廠生產IOHandle需要遵守的規則 public interface IOFactory { IOHandler createIOHandler(); }

```java //SP的物件建立過程我們放到一個工廠類裡面 public class SPIOFactory implements IOFactory { @Override public IOHandler createIOHandler() { return new SPIOHandler(); } } //記憶體方式的物件也是如此 public class MemoryIOFactory implements IOFactory { @Override public IOHandler createIOHandler() { return new MemoryIOHandler(); } }

```

對比一下呼叫方式:

java private IOHandler ioHandler; /**這裡我們通過例項化對應的SP工廠類,再通過工廠ioFactory物件呼叫createIOHandler返回的就是我們所需要的 IOHandler的子類的物件*/ IOFactory ioFactory = new SPIOFactory(); //IOFactory ioFactory = new MemoryIOFactory();當我們需要某個類的物件時候,直接替換它所對應的小工廠類即可 ioHandler = ioFactory.createIOHandler(); ioHandler.put("username", "Colin"); ioHandler.put("userAge", "33");

優點:各個不同功能的例項物件的建立程式碼,也沒有耦合在同一個工廠類裡,而是抽象了一個工廠介面作為擴充套件點,這也是工廠方法模式對簡單工廠模式解耦的一個體現。

缺點:工廠方法模式的缺點是每增加一個java類,就需要增加一個對應的工廠類,當我們的類很多的時候,那麼對應的工廠類也會很多。

三.抽象工廠模式

基於工廠方法的進一步優化的方式。

java public class IOHandlerFactory { //幫助我們建立物件通過傳入的.class判斷我們需要的類進而建立對應的物件 public static <T extends IOHandler> IOHandler createIOHandle(Class<T> tClass) { IOHandler ioHandler = null; try { ioHandler = tClass.newInstance(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } return ioHandler; } //這裡舉個SP例子 public static IOHandler createSharePreferences() { return createIOHandle(SPIOHandler.class); } //...往下我們需要什麼類的物件,就封裝一個它對應的方法,外部直接呼叫即可。 }

呼叫:

```java private IOHandler ioHandler;

ioHandler = IOHandlerFactory.createSharePreferences(); ioHandler.put("username", "Colin"); ioHandler.put("userAge", "33"); ```

優點:相比於工廠方法模式不用一直建立新的對應的類的小工廠,擴充套件性更加,不會使程式碼越來越多,越複雜,把生產物件的過程抽象化,這樣就可以和業務邏輯解耦,如果有新擴充套件,可以在IOHandlerFactory中增加對應的方法。

缺點:暫無。

Android原始碼中用到的工廠模式舉例

一.BitmapFactory 原始碼工廠模式詳解(簡單工廠)

我們先看一下BitmapFactory 這個類裡面具有的一些方法,快捷鍵ctrl+F12可檢視當前類裡面的方法,看到這我們就知道主要都是一些解碼操作的方法,最後都是通過解析傳入的圖片的資源,最後轉化成Bitmap物件。

接下來我們看一下BitmapFactory 生成 Bitmap的呼叫的過程吧,一開始我們呼叫的是這個方法傳入資源的路徑,然後往下走decodeFile(pathName, null);最後返回的是Bitmap,所以我們往下走bm = decodeStream(stream, null, opts);

```java public static Bitmap decodeFile(String pathName) { return decodeFile(pathName, null); }

public static Bitmap decodeFile(String pathName, Options opts) { validate(opts); Bitmap bm = null; InputStream stream = null; try { stream = new FileInputStream(pathName); bm = decodeStream(stream, null, opts); } catch (Exception e) { / do nothing. If the exception happened on open, bm will be null. / Log.e("BitmapFactory", "Unable to decode stream: " + e); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // do nothing here } } } return bm; } ```

進到decodeStream(stream, null, opts);方法裡面看看,

```java @Nullable public static Bitmap decodeStream(@Nullable InputStream is, @Nullable Rect outPadding, @Nullable Options opts) { // we don't throw in this case, thus allowing the caller to only check // the cache, and not force the image to be decoded. if (is == null) { return null; } validate(opts);

Bitmap bm = null;

Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
try {
    if (is instanceof AssetManager.AssetInputStream) {
        final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
        bm = nativeDecodeAsset(asset, outPadding, opts, Options.nativeInBitmap(opts),
            Options.nativeColorSpace(opts));
    } else {
        bm = decodeStreamInternal(is, outPadding, opts);
    }

    if (bm == null && opts != null && opts.inBitmap != null) {
        throw new IllegalArgumentException("Problem decoding into existing bitmap");
    }

    setDensityFromOptions(bm, opts);
} finally {
    Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
}

return bm;

} ```

我們看看這行程式碼,很明顯它的通過JNI呼叫了底層的方法來進行解碼,這裡我們暫時不深究,最後它是返回一個Bitmap物件,

java bm = nativeDecodeAsset(asset, outPadding, opts, Options.nativeInBitmap(opts), Options.nativeColorSpace(opts));

生成Bitmap的大致的流程我們都過了一邊,其實我們不必深究太多原始碼,我們呼叫者只需要知道我們傳入圖片的路徑會返回一個Bitmap就行,而正是因為它使用了工廠模式,把這些細節都遮蔽了,所以我們不用操心,甭管它生成的,我給你一個圖片路徑你給我生成一個 Bitmap 就好了。

說到這裡,可能大家還不明白這玩玩意兒哪裡體現工廠模式了,不慌,接下來繼續分析。BitmapFactory,通過傳入不同的條件,得到同樣的bitmap,回顧我們上面說的簡單工廠模式,是不是傳入我們想要的type型別就可以從工廠裡面生產出我們想要的例項物件,這是不是很類似呢。看下面的程式碼,最終都是為的是生成Bitmap物件但是我們可以傳入不同的條件引數進行獲取,我們都可以理解為在同一個工廠用不同的條件去生產相同的產品。

簡單工廠模式的實質是由一個工廠類根據傳入的引數,動態決定應該建立哪一個產品類(這些產品類繼承自一個父類或介面)的例項

但是這樣的簡單工廠模式缺點很明顯,比如我們有另外一種生產Bitmap的方法出現的時候,我們就要去修改工廠類BitmapFactory,違反了開閉原則,我們儘量不要去修改我們的基類。

```java /* * Creates Bitmap objects from various sources, including files, streams, * and byte-arrays. / public class BitmapFactory {

public static Bitmap decodeFile(String pathName, Options opts){......}

public static Bitmap decodeFile(String pathName) {......}

public static Bitmap decodeResourceStream(Resources res, TypedValue value,
        InputStream is, Rect pad, Options opts) {......}

public static Bitmap decodeResource(Resources res, int id, Options opts) {......}

public static Bitmap decodeResource(Resources res, int id) {......}

public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts){......}

public static Bitmap decodeByteArray(byte[] data, int offset, int length) {......}

public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {......}

public static Bitmap decodeStream(InputStream is) {......}

public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) {......}

public static Bitmap decodeFileDescriptor(FileDescriptor fd) {......}

} ```

二.Retrofit的工廠模式(抽象工廠)

由於Retrofit涉及的原始碼比較龐大,在這裡僅僅舉例介紹這個框架裡面所應用到的工廠模式嗎,下面是Retrofit的簡單使用方式,我們簡單分析一下GsonConverterFactory.create()和RxJavaCallAdapterFactory.create(),很明顯從名字上就可以看出是工廠模式。兩者建立的形式都是抽象工廠模式,大同小異,拿GsonConverterFactory.create()舉例說明,請看下文。

java new Retrofit.Builder() .baseUrl("http://www.baidu.com/") .client(new OkHttpClient()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build();

我們點選create()方法來到原始碼中,

```java public final class GsonConverterFactory extends Converter.Factory {

public static GsonConverterFactory create() { return create(new Gson()); } ```

點選這裡進入到抽象工廠中看看,

java Converter.Factory

可以看到Factory是abstract修飾,那麼就可以明確它是使用的是抽象工廠。

```java //這是個資料的轉換器的抽象類,泛型F表示輸入的引數,T是輸出的引數,也就是我們轉換完成之後的資料型別。 public interface Converter { //這是一個接口裡面的方法,主要做的是轉換的操作 @Nullable T convert(F value) throws IOException;

abstract class Factory { //請求響應 public @Nullable Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; }

//發起請求 public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return null; }

//該工廠用於轉換字串型別的轉換器, public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; }

protected static Type getParameterUpperBound(int index, ParameterizedType type) {
  return Utils.getParameterUpperBound(index, type);
}

protected static Class<?> getRawType(Type type) {
  return Utils.getRawType(type);
}

} } ```

接下來我們看看它的具體實現類,通過封裝一個 create 方法,來建立工廠物件,外部呼叫者就不需要關係工廠物件是如何建立的。再一個通過responseBodyConverter、requestBodyConverter 方法分別建立了請求響應和請求發起這兩種產品的物件。

```java //這裡就是建立了它的工廠物件 public static GsonConverterFactory create() { return create(new Gson()); } //這裡是通過封裝了一個create方法傳入一個Gson物件建立的工廠 public static GsonConverterFactory create(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); return new GsonConverterFactory(gson); }

@Override public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonResponseBodyConverter<>(gson, adapter); }

@Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonRequestBodyConverter<>(gson, adapter); } ```

總結:工廠模式屬於建立型的設計模式,主要設計的核心在建立我們的物件上面,使得建立物件和第三方呼叫者相隔離,這樣物件的提供方和呼叫方的耦合關係就會減小。