代码实录一

日常编程中,在阅读一些开源库的时候,总会看到一些比较优秀的代码风格/设计,雷布斯说:以前还是程序员的时候,自己不会写诗,但是同事读他的代码的时候,感觉写的像诗一样优雅。。
不积跬步,无以至千里。
以下,这些就当做是一个笔记,一份积累吧。

一.静态内部类单例模式
先看下代码:

public class Singleton {
    private Singleton() {
    }
    public static Singleton getInstance() {
        return Builder.instance;
    }
    private static class Builder {
        private static final Singleton instance = new Singleton();
    }
}

JVM虚拟机的类加载机制,当第一次加载Singleton类的时候并不会初始化instance,只有在调用getInstance()的时候,JVM才会加载Builder类,同时初始化instance。这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时还延迟了单例的实例化,所以在很多开源库中,发现很多人都使用这种单例模式。

二.面对接口编程
这个有点老生常谈了,但是这个太经典了,Java语言的多态性就是为了这个而设计的。但是有时候道理都懂,而实际敲代码的过程中,往往不一定能够运用自如,即使用了也乱七八糟。多的就不想继续写了,这篇文章不是讨论设计模式的,仅仅是记录一些优秀的代码,吸为己用。
就举一个最近看到的例子吧。是做软键盘监听的辅助工具:
主类:KeyboardWatcher,监听软键盘的弹出和关闭
接口:SoftKeyboardStateListener,根据弹出和关闭回调对应的函数
这基本是常规操作了,在需要使用的地方实现具体的回调函数及后续操作
但是作者还在该接口的基础上额外加了另一个
接口:KeyboardHelperCallback:该接口目前也只有2个方法,弹出和关闭回调对应的函数
然后写了一个抽象类实现了以上2个接口,代码如下:

public abstract class KeyboardHelper implements KeyboardWatcher.SoftKeyboardStateListener, KeyboardHelperCallback {
    @Override
    public void onSoftKeyboardOpened(int keyboardHeightInPx) {
        onOpened(keyboardHeightInPx);
    }
    @Override
    public void onSoftKeyboardClosed() {
        onClosed();
    }
}

这样做的好处是SoftKeyboardStateListener作为KeyboardWatcher的一部分,保持功能单一性(即只监听弹出和关闭)。额外加上KeyboardHelperCallback接口,一是能降低对工具类代码的侵入性。二是可以提升代码的可扩展性,比如我需要在回调函数中额外加上某个参数,这样就不需要去修改 SoftKeyboardStateListener的回调函数了。
以上,个人觉得这段代码还是挺优雅的。

三.对第三方库的封装
很多时候,项目中都会用到第三方的库,很多时候直接使用,感觉一时很爽,直到遇到了坑..
之前的某个项目,用了一个第三方的工具库,里面有个方法是ToastUtils,代码里面需要用到的弹出toast的地方都是直接使用ToastUtils.xxx,直到有一天测试说,在某个红米手机上,toast的显示异常,一查果然,该ToastUtils代码有一部分和部分红米手机的系统(怀疑是厂商对SDK定制的原因)不兼容。
由于技术的迭代和第三库可能存在bug等原因,在使用前,对第三方库进行封装是很有必须要的。
举一个对图片加载库的封装
//单例,调度第三方的图片加载框库

public class ImageLoaderManager {
    public static final int GLIDE = 1;
    public static final int PICASSO = 2;
    private static final int IMAGE_ENGINE_DEFAULT = PICASSO;

    private ImageLoaderManager() {
    }
    public static ImageLoaderManager getInstance() {
        return Build.INSTANCE;
    }
    public ImageLoader initEngine() {
        return initEngine(IMAGE_ENGINE_DEFAULT);
    }
    public ImageLoader initEngine(int engineType) {
        ImageLoader imageLoad;
        switch (engineType) {
            case GLIDE:
                imageLoad = GlideLoader.getInstance();
                break;
            case PICASSO:
                imageLoad = PicassoLoder.getInstance();
                break;
            default:
                imageLoad = GlideLoader.getInstance();
        }
        return imageLoad;
    }
    private static class Build {
        private static final ImageLoaderManager INSTANCE = new ImageLoaderManager();
    }

}

//图片加载抽象类

public abstract class ImageLoader implements ImageLoadInterface<ImageView> {
    @Override
    public ImageView createImage(Context context) {
        ImageView imageView = new ImageView(context);
        return imageView;
    }
}

//图片加载接口

public interface ImageLoadInterface<T extends View> extends Serializable {
    void displayImage(Context context, Object path, T imageView);
    T createImage(Context context);
}

//实现类,具体的第三方库Glide

public class GlideLoader extends ImageLoader {
    @Override
    public void displayImage(Context context, Object path, ImageView imageView) {
        Glide.with(context.getApplicationContext())
                .load(path)
                .placeholder(R.mipmap.df_icon)
                .into(imageView);
    }
    public static GlideLoader getInstance() {
        return GlideLoader.Builder.glideLoader;
    }
    static class Builder {
        private static GlideLoader glideLoader = new GlideLoader();

    }
}

//Picasso同上,就不写了
当然,上面的封装还是有些不足,比如图片的具体加载方法写的太死,不方便特定情况的重写,当然这里只是记录怎么封装第三方库这种思想,不做代码的精化处理。

四.对”代码三”的修改
突然上面的写法有些瑕疵,写了个demo,完善了一下,主要增加了获取引擎实体的接口,简化了调用图片加载库的方法,下面的 ImageLoadHelper=ImageLoaderManager(换了个名字)

public class ImageLoadHelper implements ImageLoaderInf<ImageView> {
    private AbstracImageLoader imageLoader;
    public static final int ENGINE_GLIDE = 1;
    private ImageLoadHelper() {
    }
    public static ImageLoadHelper getInstance() {
        return Builder.INSTANCE;
    }
    public void initEngine() {
        initEngine(ENGINE_GLIDE);
    }
    public void initEngine(int engine) {
        switch (engine) {
            case ENGINE_GLIDE:
                imageLoader = GlideLoader.getInstance();
                break;
            default:
        }
    }
    @Override
    public void display(Context context, Object file, ImageView view) {
        if (imageLoader != null) {
            imageLoader.display(context, file, view);
        }
    }
    private static class Builder {
        private static final ImageLoadHelper INSTANCE = new ImageLoadHelper();
    }
}

GlideLoader类实现EngineInf接口,通过getEngine()获取图片加载引擎,主要可以定制化个别情况的图片加载需求,比如不同的占位图等

public interface EngineInf<T> {
    T getEngine(Context context);
}
public class GlideLoader extends AbstracImageLoader implements EngineInf<RequestManager> {
    @Override
    public void display(Context context, Object file, ImageView view) {
        Glide.with(context).load(file).into(view);
    }
    private GlideLoader() {
    }
    public static GlideLoader getInstance() {
        return Builder.GLIDELOADER;
    }
    @Override
    public RequestManager getEngine(Context context) {
        return Glide.with(context);
    }
    private static class Builder {
        private static final GlideLoader GLIDELOADER = new GlideLoader();
    }
}

这样调用的时候就是:

ImageLoadHelper.getInstance().display(App.sContext, R.mipmap.ic_launcher, imageView);
可能这种写法还存在需要完善的地方吧,等到发现了问题再跟进修改..

五.Comparator接口
对某个实体类进行排序的时候,可以通过实现Comparator接口进行处理,比如,文章实体类,需要根据文章的日期进行排序,

public class ComparatorDate implements Comparator<ArchiveBean> {

    public int compare(ArchiveBean obj1, ArchiveBean obj2) {
        Date d1, d2;
        try {
            d1 = DateUtils.string2Date(obj1.getPostTime());
            d2 = DateUtils.string2Date(obj2.getPostTime());
            if (d1 == null) {
                return 1;
            }
            if (d2 == null) {
                return -1;
            }
            if (d1.before(d2)) {
                return 1;
            } else {
                return -1;
            }
        } catch (Exception e) {
            return 0;
        }
    }
}\n
List<ArchiveBean> list = new ArrayList<>();
list.addAll(...);
Collections.sort(list, new ComparatorDate());

六.接口回调的复用实例
看下这样一段代码:

public class HttpCallback<T> implements OnHttpListener<T> {

    private final OnHttpListener mListener;

    public HttpCallback(OnHttpListener listener) {
        mListener = listener;
    }

    @Override
    public void onSucceed(T result) {
        if (mListener == null) {
            return;
        }
        mListener.onSucceed(result);
    }

    @Override
    public void onFail(Exception e) {
        if (mListener == null) {
            return;
        }
        mListener.onFail(e);
    }
}

为啥HttpCallback实现了OnHttpListener接口,还要给HttpCallback的构造函数传递一个OnHttpListener接口呢?通常在需要回调的地方,直接new一个HttpCallback对象不就行了吗。答案是统一处理回调函数。看如下代码:

public class A implements OnHttpListener {
    @Override
    public void onSucceed(Object result) {
        // TODO onSucceed...
    }

    @Override
    public void onFail(Exception e) {
        // TODO onFail...
    }
}

A实现了了OnHttpListener接口,可以在onSucceed和onFail函数中统一处理某些逻辑
B是A的子类:

public class B extends A {
    private void fun1() {
        new HttpCallback(this);
    }

    private void fun2() {
        new HttpCallback(this) {
            @Override
            public void onSucceed(Object result) {
                super.onSucceed(result);
            }
        };
    }
}

B不再需要继续处理onSucceed和onFail的公共逻辑部分,且可以选择性实现自己的onSucceed和onFail方法。
还有一个问题,在HttpCallback中直接统一处理onSucceed和onFail不可以吗?
答案是,当然可以。
但是像上面那种方式有什么好处呢:
1.解耦,onSucceed和onFail处理的公共逻辑部分,大部分情况下和A的属性(变量等..)息息相关,
2.不同基类处理的公共逻辑不同,比如现在来了个C类,也要处理统一处理onSucceed和onFail方法,但是C和A的处理方式完全不一样,此时直接在HttpCallback中统一处理并不友好。

未完待续..