美文网首页
面向对象基本原则 - 开闭原则

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

作者: wenou | 来源:发表于2018-05-19 11:59 被阅读28次
    开闭原则 - (让程序更稳定,更灵活)

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

    开闭原则是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源码设计模式解析与实战》

    相关文章

      网友评论

          本文标题:面向对象基本原则 - 开闭原则

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