一. what ?
對于一個框架來說, 用戶只需要知道這個框架的關(guān)鍵組件和接口就行了, 不要對外公布太多的細(xì)節(jié). 因?yàn)橛脩艨吹降臇|西太多反而導(dǎo)致了迷惑. 對于用戶來說, 只要調(diào)用一個方法就幫我完成我想要的那些復(fù)雜功能, 這樣最好不過了. 接口和實(shí)現(xiàn)分開或者說只對外公布用戶要使用的接口, 而其實(shí)現(xiàn)則對用戶隱藏起來. 這是一個框架應(yīng)該做的事情, 也是Java的一個重要特性 ------ 封裝. 簡單的來說接口和實(shí)現(xiàn)的分離就是把接口已實(shí)現(xiàn)分開, 盡量減少兩者之間的依賴, 以方便移植和修改. 那么隱藏實(shí)現(xiàn)又怎么說呢? 前面已經(jīng)說了, 一個框架要做到的是盡量不要公布實(shí)現(xiàn), 只公布接口. 因此就需要對實(shí)現(xiàn)進(jìn)行封裝并隱藏. 這樣說有些抽象, 你可能有些不知所云. 下面我將說說為什要進(jìn)行接口和實(shí)現(xiàn)的分離、對實(shí)現(xiàn)方式進(jìn)行隱藏 以及怎么實(shí)現(xiàn)它們.
二. why ?
在《在Android上使用SPI機(jī)制》一文中已經(jīng)說過關(guān)于接口和實(shí)現(xiàn)的分離和動態(tài)更換實(shí)現(xiàn)的問題, 但是接口的實(shí)現(xiàn)并未對外隱藏, 用戶可以直接調(diào)用接口的實(shí)現(xiàn), 而不使用接口. 這樣編寫的代碼并不是面向接口編程, 而是硬編碼. 如果要更換實(shí)現(xiàn), 這將非常麻煩. 為了不讓用戶看到實(shí)現(xiàn), 只需要將實(shí)現(xiàn)類變成包私有的類用反射初始化. 分離接口和實(shí)現(xiàn)以及隱藏實(shí)現(xiàn)細(xì)節(jié)好處很多, 下面列舉幾個:
- 面向接口編程, 方便更換實(shí)現(xiàn).
- 隱藏實(shí)現(xiàn)細(xì)節(jié), 減少對外接口和類.
- 減少接口和實(shí)現(xiàn)直接的相互依賴.
- 封裝, 高內(nèi)聚.
- ......
三. how ?
下面看看如何實(shí)現(xiàn):
- (1) 定義接口
public interface ImageLoader {
/**
* 初始化ImageLoader
* @param appContext ApplicatonContext
*/
void init(@NonNull Context appContext);
/**
* 展示圖片
* @param targetView
* @param uri
* @param listener
*/
void displayImage(@NonNull ImageView targetView, @NonNull Uri uri, @Nullable LoadListener listener);
/**
* 取消圖片展示
* @param targetView
*/
void cancelDisplay(ImageView targetView);
/**
* 銷毀ImageLoader, 回收資源
*/
void destroy();
}
- (2) 實(shí)現(xiàn)接口 (實(shí)現(xiàn)類要定義成包私有的, 即沒有修飾符)
FrescoImageLoader.java文件:
class FrescoImageLoader implements ImageLoader {
private Context mAppContext;
@Override
public void init(@NonNull Context appContext) {
if(Fresco.hasBeenInitialized()) return;
// hold appContext
mAppContext = appContext;
// init fresco
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(chain -> {
DevUtil.d("ImageLoader", "request-url: " + chain.request().url().toString());
return chain.proceed(chain.request());
})
.build();
ImagePipelineConfig config = OkHttpImagePipelineConfigFactory
.newBuilder(appContext, client)
.build();
Fresco.initialize(appContext, config);
}
@Override
public void displayImage(@NonNull ImageView targetView, @NonNull Uri uri, @Nullable LoadListener listener) {
// Fresco
if (targetView instanceof DraweeView) {
DraweeView realView = (DraweeView) targetView;
realView.setController(getDraweeController(realView, uri, listener));
return;
}
// Generic ImageView
targetView.setImageURI(uri);
}
private DraweeController getDraweeController(DraweeView targetView, Uri uri, LoadListener listener) {
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(targetView.getController())
.setUri(uri)
.setControllerListener(listener == null ? null : new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(String id, Throwable throwable) {
super.onFailure(id, throwable);
if (listener != null) {
listener.onFailed(targetView);
}
}
@Override
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
super.onFinalImageSet(id, imageInfo, animatable);
if (imageInfo instanceof CloseableBitmap) {
CloseableBitmap image = (CloseableBitmap) imageInfo;
Bitmap resultBitmap = image.getUnderlyingBitmap();
if (listener != null) {
listener.onSuccess(targetView, resultBitmap);
}
}
}
})
.build();
return controller;
}
@Override
public void cancelDisplay(ImageView targetView) {
}
@Override
public void destroy() {
Fresco.shutDown();
}
}
UILImageLoader.java文件
class UILImageLoader implements ImageLoader {
private com.nostra13.universalimageloader.core.ImageLoader mImpl;
@Override
public void init(@NonNull Context appContext) {
mImpl = com.nostra13.universalimageloader.core.ImageLoader.getInstance();
}
@Override
public void displayImage(@NonNull ImageView targetView, @NonNull Uri uri, @Nullable LoadListener listener) {
com.nostra13.universalimageloader.core.ImageLoader.getInstance().displayImage(uri.toString(), targetView);
}
@Override
public void cancelDisplay(ImageView targetView) {
mImpl.cancelDisplayTask(targetView);
}
@Override
public void destroy() {
mImpl.destroy();
}
}
其余省略................
- (3) 為實(shí)現(xiàn)類定義初始化工廠類
class ImageLoaderFactory {
private static ImageLoader mImageLoader;
private ImageLoaderFactory() {
//no instance
}
/**
* @return
*/
public static ImageLoader createImageLoader(String implClass) {
if (mImageLoader == null) {
mImageLoader = createImageLoaderWithClassName(implClass);
}
return mImageLoader;
}
/**
* 此處使用類反射, 所有implClass都不能混淆, 類名必須keep: {@code -keep class a.b.c.ImplClass}
* @param implClass 實(shí)現(xiàn)類有: FrescoImageLoader, GlideImageLoader, PicassoImageLoader, UILImageLoader
* @return
*/
private static ImageLoader createImageLoaderWithClassName(String implClass) {
try {
Class klass = Class.forName(implClass);
Constructor constructor = klass.getDeclaredConstructor();
if (constructor == null) {
throw new RuntimeException(implClass + " 的實(shí)現(xiàn)類必須有一個無參構(gòu)造方法 !");
}
boolean isAccessible = constructor.isAccessible();
constructor.setAccessible(true);
Object obj = constructor.newInstance();
constructor.setAccessible(isAccessible);
if ( !(obj instanceof ImageLoader) ) {
throw new RuntimeException(implClass + "必須實(shí)現(xiàn)" + ImageLoader.class.getName() + "接口");
}
ImageLoader imageLoader = (ImageLoader) obj;
return imageLoader;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
- (4) 要使用ImageLoader, 直接用工廠類創(chuàng)建即可, 我們只需要知道實(shí)現(xiàn)類的名稱, 其它細(xì)節(jié)都不需要知道. 實(shí)現(xiàn)類都是高度內(nèi)聚, 外部完全不知道其內(nèi)部的邏輯. 外部只需要調(diào)用接口的方法實(shí)現(xiàn)業(yè)務(wù)邏輯即可. 下面是ImageLoader及其相關(guān)實(shí)現(xiàn)的整體結(jié)構(gòu):

- (5) 下面是使用工廠類創(chuàng)建ImageLoader實(shí)現(xiàn)的代碼
import android.content.Context;
public final class ImageManager {
private static String TAG = "ImageManager";
private static ImageLoader sImageLoader;
private ImageManager() {
//no instance
}
public static void init(Context appContext) {
if (sImageLoader != null) {
throw new IllegalStateException(TAG + " already initalized");
}
sImageLoader = ImageLoaderFactory.createImageLoader("com.stone.app.manager.imageloader.internal.FrescoImageLoader");
sImageLoader.init(appContext);
}
public static ImageLoader getImageLoader() {
return sImageLoader;
}
}
總結(jié):
- 將接口和實(shí)現(xiàn)類分離, 接口和實(shí)現(xiàn)分別放在單獨(dú)的包中, 并且實(shí)現(xiàn)類定義為包私有的 (即類沒有修飾符).
- 定義工廠類, 使用反射初始化實(shí)現(xiàn)類.
- 注意混淆的時候不能混淆實(shí)現(xiàn)類的類名, 因?yàn)槠涑跏蓟褂昧朔瓷?
有一個設(shè)計(jì)模式也是分離接口和實(shí)現(xiàn)并使其各自獨(dú)立變化, 那就是橋接模式. 具體可以參考Android中的Window和Context的設(shè)計(jì). 關(guān)于動態(tài)擴(kuò)展和分離接口和實(shí)現(xiàn), 可以參考《在Android上使用SPI機(jī)制》