美文网首页Android架构今日看点程序员
谈谈Java接口与实现的分离以及隐藏实现

谈谈Java接口与实现的分离以及隐藏实现

作者: 元亨利贞o | 来源:发表于2016-09-18 16:40 被阅读2721次

    一. what ?
    对于一个框架来说, 用户只需要知道这个框架的关键组件和接口就行了, 不要对外公布太多的细节. 因为用户看到的东西太多反而导致了迷惑. 对于用户来说, 只要调用一个方法就帮我完成我想要的那些复杂功能, 这样最好不过了. 接口和实现分开或者说只对外公布用户要使用的接口, 而其实现则对用户隐藏起来. 这是一个框架应该做的事情, 也是Java的一个重要特性 ------ 封装. 简单的来说接口和实现的分离就是把接口已实现分开, 尽量减少两者之间的依赖, 以方便移植和修改. 那么隐藏实现又怎么说呢? 前面已经说了, 一个框架要做到的是尽量不要公布实现, 只公布接口. 因此就需要对实现进行封装并隐藏. 这样说有些抽象, 你可能有些不知所云. 下面我将说说为什要进行接口和实现的分离对实现方式进行隐藏 以及怎么实现它们.

    二. why ?
    《在Android上使用SPI机制》一文中已经说过关于接口和实现的分离和动态更换实现的问题, 但是接口的实现并未对外隐藏, 用户可以直接调用接口的实现, 而不使用接口. 这样编写的代码并不是面向接口编程, 而是硬编码. 如果要更换实现, 这将非常麻烦. 为了不让用户看到实现, 只需要将实现类变成包私有的类用反射初始化. 分离接口和实现以及隐藏实现细节好处很多, 下面列举几个:

    1. 面向接口编程, 方便更换实现.
    2. 隐藏实现细节, 减少对外接口和类.
    3. 减少接口和实现直接的相互依赖.
    4. 封装, 高内聚.
    5. ......

    三. how ?
    下面看看如何实现:

    • (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) 实现接口 (实现类要定义成包私有的, 即没有修饰符)
      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) 为实现类定义初始化工厂类
    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 实现类有: 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 + " 的实现类必须有一个无参构造方法 !");
                }
                boolean isAccessible = constructor.isAccessible();
                constructor.setAccessible(true);
                Object obj = constructor.newInstance();
                constructor.setAccessible(isAccessible);
                if ( !(obj instanceof ImageLoader) ) {
                    throw new RuntimeException(implClass + "必须实现" + 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, 直接用工厂类创建即可, 我们只需要知道实现类的名称, 其它细节都不需要知道. 实现类都是高度内聚, 外部完全不知道其内部的逻辑. 外部只需要调用接口的方法实现业务逻辑即可. 下面是ImageLoader及其相关实现的整体结构:
    接口与实现分离, 隐藏实现.png
    • (5) 下面是使用工厂类创建ImageLoader实现的代码
    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;
        }
    }
    

    总结:

    1. 将接口和实现类分离, 接口和实现分别放在单独的包中, 并且实现类定义为包私有的 (即类没有修饰符).
    2. 定义工厂类, 使用反射初始化实现类.
    3. 注意混淆的时候不能混淆实现类的类名, 因为其初始化使用了反射.

    有一个设计模式也是分离接口和实现并使其各自独立变化, 那就是桥接模式. 具体可以参考Android中的Window和Context的设计. 关于动态扩展分离接口和实现, 可以参考《在Android上使用SPI机制》

    相关文章

      网友评论

      本文标题:谈谈Java接口与实现的分离以及隐藏实现

      本文链接:https://www.haomeiwen.com/subject/jqdiettx.html