面向对象基本原则 - 开闭原则

2018-05-19  本文已影响28人  wenou
开闭原则 - (让程序更稳定,更灵活)

上一篇:
面向对象基本原则 - 单一职责

开闭原则是java里最基础的设计原则
定义是:对象(类,模块,函数等)应该对于扩展是开放的,对于修改是封闭的

当需要对代码进行修改,这个时候应该尽量去扩展原来的代码,而不是去修改原来的代码,修改原来的代码就有可能会引起其他的问题

这里还是拿上一篇 单一职责 的例子来说明

由于之前的ImageLoader只有内存缓存
有一天,我们想要增加功能,给ImageLoader增加SD卡本地缓存,并且提供一个方法,让调用者来选择使用内存缓存还是本地缓存,于是代码就要修改了

添加本地缓存DiskCache类,这里只是学习基本原则,就不使用系统的DiskLruCache类了

public class DiskCache {
    private static final String cacheDir = "sdcard/cache";

    public Bitmap get(String imageUrl){
        return BitmapFactory.decodeFile(cacheDir + imageUrl);
    }

    public void put(String imageUrl,Bitmap bitmap){
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(cacheDir + imageUrl);
            bitmap.compress(Bitmap.CompressFormat.PNG,100,fileOutputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            if(fileOutputStream != null){
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

于是ImageLoader就修改成这样:

public class ImageLoader {

    private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    private ImageCache mImageCache = new ImageCache();
    private DiskCache mDiskCache = new DiskCache();

    private boolean isDiskCache;
    //对外提供方法设置,是否是本地缓存
    public void setDiskCache(boolean diskCache) {
        isDiskCache = diskCache;
    }

    public void displayImage(final String url, final ImageView imageView) {
        //判断是本地缓存还是内存缓存
        Bitmap bitmap = isDiskCache ? mDiskCache.get(url) : mImageCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(url);
        //没有缓存,提交到线程池下载
        submitLoad(url, imageView);
    }
    ...
}

代码看起来也还可以,但是如果有一天,我们希望内存和本地两种缓存都使用,如果内存缓存有就从内存里面取,内存没有再从本地里面取,本地也没有,再从网络上下载图片

修改代码,添加一个双缓存类DoubleCache

public class DoubleCache {
    private ImageCache mMemoryCache = new ImageCache();
    private DiskCache mDiskCache = new DiskCache();

    public Bitmap get(String url){
        Bitmap bitmap = mMemoryCache.get(url);
        if(bitmap == null){
            bitmap = mDiskCache.get(url);
            mMemoryCache.put(url,bitmap);
        }
        return bitmap;
    }
    public void put(String url,Bitmap bitmap){
        mMemoryCache.put(url,bitmap);
        mDiskCache.put(url,bitmap);
    }
}

然后ImageLoader也要修改判断了

public class ImageLoader {

    private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    private ImageCache mMemoryCache = new ImageCache();
    private DiskCache mDiskCache = new DiskCache();
    private DoubleCache mDoubleCache = new DoubleCache();

    private boolean isDiskCache;
    private boolean isDoubleCache;

    //对外提供方法设置,是否是本地缓存
    public void setDiskCache(boolean diskCache) {
        isDiskCache = diskCache;
    }
    //是否使用双重缓存
    public void setDoubleCache(boolean doubleCache) {
        isDoubleCache = doubleCache;
    }

    public void displayImage(final String url, final ImageView imageView) {
        //判断是本地缓存还是内存缓存
        Bitmap bitmap = null;
        if(isDoubleCache){
            bitmap = mDoubleCache.get(url);
        }else if(isDiskCache){
            bitmap = mDiskCache.get(url);
        }else {
            bitmap = mMemoryCache.get(url);
        }
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(url);
        //没有缓存,提交到线程池下载
        submitLoad(url, imageView);
    }
}

到这里就会发现,每次修改缓存功能的时候,都要修改ImageLoader类,然后通过一系列的判断来选择使用那种缓存,使得if-else越来越多,代码越来越复杂,如果不小心写错某个if判断,容易出现错误,也会让ImageLoader越来越臃肿,更重要的是,用户不能实现自己的缓存注入到ImageLoader中,可扩展性差

而开闭原则指明,对象(类,模块,函数等)应该对于扩展是开放的,对于修改是封闭的,所以这里应该重构ImageLoader的代码,使得结构更加清晰,稳定

结构图

抽象一个顶层接口,各种缓存来实现这个接口,统一管理

public interface ImageCache {
    Bitmap get(String url);
    void put(String url,Bitmap bitmap);
}
public class ImageLoader {

    private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    //默认实现
    private ImageCache mImageCache = new MemoryCache();

    public void setImageCache(ImageCache cache){
        mImageCache = cache;
    }

    public void displayImage(final String url, final ImageView imageView) {
        Bitmap bitmap = mImageCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(url);
        //没有缓存,提交到线程池下载
        submitLoad(url, imageView);
    }
}

使用:

ImageLoader imageLoader = new ImageLoader();
//只使用内存缓存
imageLoader.setImageCache(new MemoryCache());
//只使用本地缓存
imageLoader.setImageCache(new DiskCache());
//使用双缓存
imageLoader.setImageCache(new DoubleCache());
//自定义缓存图片实现
imageLoader.setImageCache(new ImageCache() {
     @Override
     public Bitmap get(String url) {
          return null;
     }
     @Override
     public void put(String url, Bitmap bitmap) {
           //自定义缓存图片
      }
});

重构后的代码没有了那么多 if-else判断,少了各种各样的缓存对象实例,代码清晰简洁.ImageLoader变得更加稳定,扩展性和灵活性更高,当需要自定义缓存的时候,实现ImageCache接口就可以,并且通过setImageCache()方法注入到ImageLoader

遵循开闭原则的最重要的一点是抽象,用抽象去构建框架,用实现扩展细节;这样当发生修改的时候,直接实现抽象,派生一个类去实现不同的修改

下一篇:
面向对象基本原则 - 里氏替换 - 依赖倒置

参考资料:
《Android源码设计模式解析与实战》

上一篇下一篇

猜你喜欢

热点阅读