Android創建型設計模式(簡單工廠,工廠方法,抽象工廠,BitmapFactory簡單工廠分析,Retrofit抽象工廠分析)
[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
@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("https://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
abstract class Factory {
//請求響應
public @Nullable Converter
//發起請求 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
@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); } ```
總結:工廠模式屬於創建型的設計模式,主要設計的核心在創建我們的對象上面,使得創建對象和第三方調用者相隔離,這樣對象的提供方和調用方的耦合關係就會減小。