談?wù)凧ava接口與實(shí)現(xiàn)的分離以及隱藏實(shí)現(xiàn)

一. 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é)好處很多, 下面列舉幾個:

  1. 面向接口編程, 方便更換實(shí)現(xiàn).
  2. 隱藏實(shí)現(xiàn)細(xì)節(jié), 減少對外接口和類.
  3. 減少接口和實(shí)現(xiàn)直接的相互依賴.
  4. 封裝, 高內(nèi)聚.
  5. ......

三. 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):
接口與實(shí)現(xiàn)分離, 隱藏實(shí)現(xiàn).png
  • (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é):

  1. 將接口和實(shí)現(xiàn)類分離, 接口和實(shí)現(xiàn)分別放在單獨(dú)的包中, 并且實(shí)現(xiàn)類定義為包私有的 (即類沒有修飾符).
  2. 定義工廠類, 使用反射初始化實(shí)現(xiàn)類.
  3. 注意混淆的時候不能混淆實(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ī)制》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,540評論 19 139
  • 本文包括:1、Listener簡介2、Servlet監(jiān)聽器3、監(jiān)聽三個域?qū)ο髣?chuàng)建和銷毀的事件監(jiān)聽器4、監(jiān)聽三個域?qū)?..
    廖少少閱讀 6,634評論 6 28
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,940評論 25 709
  • 同樣是使用Java語言,為什么做MobileAPI的開發(fā)人員寫不了Android程序,反之亦然。我想大概是各行有各...
    lookid閱讀 925評論 1 2
  • 2017年9月16日8點(diǎn)半,當(dāng)很多人還在享受慵懶周末時,一群熱愛閱讀的新教育螢火蟲青島分站的小小閱讀領(lǐng)跑者...
    yayama2001閱讀 281評論 0 0

友情鏈接更多精彩內(nèi)容