模式的定義
簡單工廠模式又稱爲靜態方法工廠模式,是由一個工廠對象決定創建哪一個產品類的實例。
使用場景
客戶端需要創建對象、隱藏對象的創建過程,且目標對象類型數量不多的情況下,可以考慮使用簡單工廠模式。
UML類圖
角色介紹
Product
產品的通用接口,定義產品的行爲。
ConcreteProduct
具體產品類,實現了Product接口。
Creator
工廠類,通過靜態工廠方法factory來創建對象。
簡單示例:
下面以一個簡單的示例來說明進一步理解上述的幾個角色。
蘋果公司的Iphone系列產品是自從上市以來受到大家的普遍歡迎,但是對於程序員來說,創建一個Iphone產品對象的過程是複雜的,而普通的客戶程序並不需要知道這個:
package com.dp.example.simplefactory;
/**
* Iphone產品類, 對應Product角色
* @author mrsimple
*
*/
public interface Iphone {
/**
* 打電話
*/
public void call();
/**
* 發短信
*/
public void sendMessage();
/**
* 上網
*/
public void surfTheInternet();
}
package com.dp.example.simplefactory;
/**
* Iphone5s 類,對應ConcreteProduct角色
*
* @author mrsimple
*
*/
public class Iphone4s implements Iphone {
@Override
public void call() {
System.out.println("用Iphone4s打電話");
}
@Override
public void sendMessage() {
System.out.println("用Iphone4s發短信");
}
@Override
public void surfTheInternet() {
System.out.println("用Iphone4s上網");
}
}
package com.dp.example.simplefactory;
/**
* Iphone5s 類,對應ConcreteProduct角色
*
* @author mrsimple
*
*/
public class Iphone5s implements Iphone {
@Override
public void call() {
System.out.println("用Iphone5s打電話");
}
@Override
public void sendMessage() {
System.out.println("用Iphone5s發短信");
}
@Override
public void surfTheInternet() {
System.out.println("用Iphone5s上網");
}
}
package com.dp.example.simplefactory;
/**
* 工廠類
*
* @author mrsimple
*
*/
public final class IphoneFactory {
/**
* 創建產品
*
* @param type
* @return
*/
public static Iphone createIphone(String type) {
if (type == null) {
return null;
}
Iphone iphone = null;
if (type.equals("5s")) {
iphone = new Iphone5s();
} else if (type.equals("4s")) {
iphone = new Iphone4s();
}
return iphone;
}
}
使用實例:
public static void main(String[] args) {
Iphone iphone = IphoneFactory.createIphone(“4s”) ;
iphone.sendMessage();
iphone.surfTheInternet();
iphone = IphoneFactory.createIphone("5s") ;
iphone.sendMessage();
iphone.surfTheInternet();
}
源碼分析:
在Android中,我們經常使用靜態工廠方法的地方應該是創建Bitmap對象的時候,例如通過資源id獲取Bitmap對象。
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher) ;
看看BitmapFactory的工廠方法具體實現:
/**
* Synonym for {@link #decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}
* will null Options.
*
* @param res The resources object containing the image data
* @param id The resource id of the image data
* @return The decoded bitmap, or null if the image could not be decode.
*/
public static Bitmap decodeResource(Resources res, int id) {
return decodeResource(res, id, null);
}
**
* Synonym for opening the given resource and calling
* {@link #decodeResourceStream}.
*
* @param res The resources object containing the image data
* @param id The resource id of the image data
* @param opts null-ok; Options that control downsampling and whether the
* image should be completely decoded, or just is size returned.
* @return The decoded bitmap, or null if the image data could not be
* decoded, or, if opts is non-null, if opts requested only the
* size be returned (in opts.outWidth and opts.outHeight)
*/
public static Bitmap decodeResource(Resources res, int id, Options opts) {
Bitmap bm = null;
InputStream is = null;
try {
final TypedValue value = new TypedValue();
is = res.openRawResource(id, value);
bm = decodeResourceStream(res, value, is, null, opts);
} catch (Exception e) {
/* do nothing.
If the exception happened on open, bm will be null.
If it happened on close, bm is still valid.
*/
} finally {
try {
if (is != null) is.close();
} catch (IOException e) {
// Ignore
}
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
return bm;
}
可以看到,decodeResource(Resourcesres,intid)函數調用的是decodeResource(Resourcesres,intid, Options opts), 在decodeResource函數中,最終把傳遞進來的資源id解析成InputStream, 然後稱調用decodeResourceStream(res, value, is, null,opts)方法。再看decodeResourceStream的實現:
/**
* Decode a new Bitmap from an InputStream. This InputStream was obtained from
* resources, which we pass to be able to scale the bitmap accordingly.
*/
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
/**
* Decode an input stream into a bitmap. If the input stream is null, or
* cannot be used to decode a bitmap, the function returns null.
* The stream's position will be where ever it was after the encoded data
* was read.
*
* @param is The input stream that holds the raw data to be decoded into a
* bitmap.
* @param outPadding If not null, return the padding rect for the bitmap if
* it exists, otherwise set padding to [-1,-1,-1,-1]. If
* no bitmap is returned (null) then padding is
* unchanged.
* @param opts null-ok; Options that control downsampling and whether the
* image should be completely decoded, or just is size returned.
* @return The decoded bitmap, or null if the image data could not be
* decoded, or, if opts is non-null, if opts requested only the
* size be returned (in opts.outWidth and opts.outHeight)
*/
public static Bitmap decodeStream(InputStream is, Rect outPadding, 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;
}
// we need mark/reset to work properly
if (!is.markSupported()) {
is = new BufferedInputStream(is, 16 * 1024);
}
// so we can call reset() if a given codec gives up after reading up to
// this many bytes. FIXME: need to find out from the codecs what this
// value should be.
is.mark(1024);
Bitmap bm;
if (is instanceof AssetManager.AssetInputStream) {
bm = nativeDecodeAsset(((AssetManager.AssetInputStream) is).getAssetInt(),
outPadding, opts);
} else {
// pass some temp storage down to the native code. 1024 is made up,
// but should be large enough to avoid too many small calls back
// into is.read(...) This number is not related to the value passed
// to mark(...) above.
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
if (tempStorage == null) tempStorage = new byte[16 * 1024];
bm = nativeDecodeStream(is, tempStorage, outPadding, opts);
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
return finishDecode(bm, outPadding, opts);
}
private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
if (bm == null || opts == null) {
return bm;
}
final int density = opts.inDensity;
if (density == 0) {
return bm;
}
bm.setDensity(density);
final int targetDensity = opts.inTargetDensity;
if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
return bm;
}
byte[] np = bm.getNinePatchChunk();
final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
if (opts.inScaled || isNinePatch) {
float scale = targetDensity / (float)density;
// TODO: This is very inefficient and should be done in native by Skia
final Bitmap oldBitmap = bm;
bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
(int) (bm.getHeight() * scale + 0.5f), true);
oldBitmap.recycle();
if (isNinePatch) {
np = nativeScaleNinePatch(np, scale, outPadding);
bm.setNinePatchChunk(np);
}
bm.setDensity(targetDensity);
}
return bm;
}
decodeResourceStream初始化一些配置和像素信息以後,調用decodeStream(is,pad,opts),最終調用nativeDecodeAsset或者nativeDecodeStream來構建Bitmap對象,這兩個都是native方法( Android中使用Skia庫來解析圖像 ),本文不進行討論。最後調用finishDecode函數來設置像素、配置信息、縮放等參數,最終返回Bitmap對象。
BitmapFactory中的decodeFile、decodeByteArray工廠方法都是這麼一個類似的過程,BitmapFactory通過不同的工廠方法與傳遞不同的參數調用不同的圖像解析函數來構造Bitmap對象,與上文IphoneFactory通過不同類型參數構造不同的Iphone手機思路相似。
優點和缺點
優點:
1、分工明確,各司其職;
2、客戶端不再創建對象,而是把創建對象的職責交給了具體的工廠去創建;
3、使抽象與實現分離, 客戶程序不知道具體實現;
4、具名工廠函數,更能體現出代碼含義。
缺點:
1、工廠的靜態方法無法被繼承;
2、代碼維護不易,對象要是很多的話,工廠是一個很龐大的類;
3、違反開閉原則,如果有新的產品加入到系統中就要修改工廠類。