美文网首页
开放 — 封闭原则

开放 — 封闭原则

作者: wuchao226 | 来源:发表于2019-04-17 17:37 被阅读0次

    在软件设计模式中,这种不能修改,但可以扩展的思想是重要的一种设计原则,是开放—封闭原则(The Open-Closeed Principle,简称OCP)或叫开-闭原则。

    开放—封闭原则的定义是:软件中的对象(类、模块、函数等等)应该对于扩展是开放的,对于修改是封闭的。

    单一职责原则一文中我们写了 ImageLoader 图片加载类,现在我们引入SD卡缓存,这样下载过的图片就会缓存到本地

    /**
     * 图片缓存到SD卡中
     */
    public class DiskCache {
        static String cacheDir = "sdcard/cache/";
    
        /**
         * 从缓存中获取图片
         */
        public Bitmap get(String url) {
            return BitmapFactory.decodeFile(cacheDir + url);
        }
    
        /**
         * 将图片缓存到SD卡中
         */
        public void put(String url, Bitmap bitmap) {
            FileOutputStream fileOutputStream = null;
            try {
                fileOutputStream = new FileOutputStream(cacheDir + url);
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }finally {
                if (fileOutputStream!=null){
                    try {
                        fileOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    因为需要将图片缓存到SD卡中,ImageLoader 代码有所更新,具体代码如下。

    /**
     * 图片加载器
     */
    public class ImageLoader {
        /**
         * 图片缓存
         */
        ImageCache mImageCache;
        /**
         * SD卡缓存
         */
        DiskCache mDiskCache=new DiskCache();
        /**
         * 是否使用SD卡缓存
         */
        boolean isUseDiskCache=false;
        /**
         * 线程池,线程数量为CPU数量
         * Runtime.getRuntime().availableProcessors() 方法:返回可用处理器的Java虚拟机的数量。
         */
        ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    
       /**
         * 加载图片
         */
        public void displayImage(final String url, final ImageView imageView) {
            //判断使用哪种缓存
            Bitmap bitmap = isUseDiskCache?mDiskCache.get(url):mImageCache.get(url);
            if (bitmap != null) {
                imageView.setImageBitmap(bitmap);
                return;
            }
            imageView.setTag(url);
            mExecutorService.submit(new Runnable() {
                @Override
                public void run() {
                    Bitmap bitmap = downloadImage(url);
                    if (bitmap == null) {
                        return;
                    }
                    if (imageView.getTag().equals(url)) {
                        updateImageView(imageView, bitmap);
                    }
                    mImageCache.put(url, bitmap);
                }
            });
        }
    
        public void useDiskCache(boolean useDiskCache) {
            isUseDiskCache = useDiskCache;
        }
    }
    

    从上述代码中可以看到,新增了一个 DiskCache 类和往 ImageLoader 类中加入了少量代码就添加了 SD 卡缓存的功能,用户可以通过 useDiskCache 方法来对使用哪种缓存进行设置,如:

    ImageLoader imageLoader=new ImageLoader();
    //使用SD卡缓存
    imageLoader.useDiskCache(true);
    //使用内存缓存
    imageLoader.useDiskCache(false);
    

    有个问题就是每次加入新的缓存方法时都要修改原来的代码,这样很可能引入bug,而且会使原来的代码逻辑越来越复杂。

    软件中的对象(类、模块、函数等)应该对于扩展是开放的,对于修改是封闭的,这就是开放—关闭原则。也就是说,当软件需求变化时,应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。

    下面对 ImageLoader 重构,具体代码如下。

    /**
     * 图片加载器
     */
    public class ImageLoader {
        /**
         * 图片缓存
         */
        ImageCache mImageCache = new MemoryCache();
    
        /**
         * 线程池,线程数量为CPU数量
         * Runtime.getRuntime().availableProcessors() 方法:返回可用处理器的Java虚拟机的数量。
         */
        ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        /**
         * UI Handler
         */
        Handler mUiHandler = new Handler(Looper.getMainLooper());
    
        /**
         * 注入缓存实现
         */
        public void setImageCache(ImageCache imageCache) {
            mImageCache = imageCache;
        }
    
        /**
         * 加载图片
         */
        public void displayImage(final String url, final ImageView imageView) {
            Bitmap bitmap = mImageCache.get(url);
            if (bitmap != null) {
                imageView.setImageBitmap(bitmap);
                return;
            }
            submitLoadRequest(url, imageView);
        }
    
        private void submitLoadRequest(final String imageUrl, final ImageView imageView) {
            imageView.setTag(imageUrl);
            mExecutorService.submit(new Runnable() {
                @Override
                public void run() {
                    Bitmap bitmap = downloadImage(imageUrl);
                    if (bitmap == null) {
                        return;
                    }
                    if (imageView.getTag().equals(imageUrl)) {
                        updateImageView(imageView, bitmap);
                    }
                    mImageCache.put(imageUrl, bitmap);
                }
            });
        }
    
        private Bitmap downloadImage(String imageUrl) {
            Bitmap bitmap = null;
            try {
                URL url = new URL(imageUrl);
                final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                bitmap = BitmapFactory.decodeStream(conn.getInputStream());
                conn.disconnect();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bitmap;
        }
    
        private void updateImageView(final ImageView imageView, Bitmap bitmap) {
            mUiHandler.post(new Runnable() {
                @Override
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    }
    

    这里把 ImageCache 提取为一个图片缓存的接口,用来抽象图片缓存的功能,接口声明如下:

    /**
     * 图片缓存接口,用来抽象图片缓存的功能
     */
    public interface ImageCache {
        public Bitmap get(String url);
        public void put(String url, Bitmap bitmap);
    }
    

    ImageCache 接口定义了获取、缓存图片两个函数,缓存的key是图片的url,值是图片本身。内存缓存、SD卡缓存、双缓存都实现了该接口,我们看看这几个缓存实现。

    /**
     * 内存缓存MemoryCache类
     * 图片的内存缓存,key为图片的uri,值为图片本身
     */
    public class MemoryCache implements ImageCache {
        private LruCache<String, Bitmap> mMemoryCache;
    
        public MemoryCache() {
            //初始化LRU缓存
            // 计算可使用的最大内存
            final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    
            // 取4分之一的可用内存作为缓存
            final int cacheSize = maxMemory / 4;
            mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
    
                @Override
                protected int sizeOf(String key, Bitmap bitmap) {
                    //int size = bitmap.getRowBytes() * bitmap.getHeight();
                    //获取大小,Bitmap所占用的内存空间数等于Bitmap的每一行所占用的空间数乘以Bitmap的行数
                    return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
                }
            };
        }
    
        @Override
        public Bitmap get(String url) {
            return mMemoryCache.get(url);
        }
    
        @Override
        public void put(String url, Bitmap bitmap) {
            mMemoryCache.put(url, bitmap);
        }
    }
    
    /**
     * SD卡缓存 DiskCache 类
     * 图片缓存到SD卡中
     */
    public class DiskCache implements ImageCache {
        static String cacheDir = "sdcard/cache/";
    
        /**
         * 从本地文件中获取图片
         */
        @Override
        public Bitmap get(String url) {
            return BitmapFactory.decodeFile(cacheDir + url);
        }
    
        /**
         * 将bitmap写入文件中
         */
        @Override
        public void put(String url, Bitmap bitmap) {
            FileOutputStream fileOutputStream = null;
            try {
                fileOutputStream = new FileOutputStream(cacheDir + url);
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } finally {
                if (fileOutputStream != null) {
                    try {
                        fileOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    /**
     * 双缓存 DoubleCache 类
     */
    public class DoubleCache implements ImageCache {
    
        ImageCache mMemoryCache = new MemoryCache();
        ImageCache mDiskCache = new DiskCache();
    
        /**
         * 先从缓存中获取图片,如果没有,在从SD卡中获取
         */
        @Override
        public Bitmap get(String url) {
            Bitmap bitmap = mMemoryCache.get(url);
            if (bitmap == null) {
                bitmap = mDiskCache.get(url);
            }
            return bitmap;
        }
    
        /**
         * 将图片缓存到内存和SD卡中
         */
        @Override
        public void put(String url, Bitmap bitmap) {
            mMemoryCache.put(url, bitmap);
            mDiskCache.put(url, bitmap);
        }
    }
    

    我们在 ImageLoader 类中增加了一个setImageCache(ImageCache imageCache)函数,用户可以通过该函数设置缓存实现,也就是通常说的依赖注入。设置缓存实现的方式如下:

    ImageLoader imageLoader = new ImageLoader();
    //使用内存缓存
    imageLoader.setImageCache(new MemoryCache);
    //使用 SD 卡缓存
    imageLoader.setImageCache(new DiskCache);
    //使用双缓存
    imageLoader.setImageCache(new DoubleCache);
    //使用自定义图片缓存
    imageLoader.setImageCache(new ImageCache(){
        @Override
        public void put(String url, Bitmap bitmap) {
          //缓存图片
        }
    
        @Override
        public Bitmap get(String url) {
          /* 从缓存中获取图片 */
          return null;
        }
    });
    

    在上述代码中,通过 setImageCache(ImageCache imageCache) 方法注入不同的缓存实现,这样不仅能够使 ImageLoader 更简单、健壮,也使 ImageLoader 的可扩展性、灵活性更高。MemoryCache、DiskCache、DoubleCache 缓存图片的具体实现完全不一样,但它们都实现了 ImageCache 接口。当用户需要自定义实现缓存策略时,只需新建一个实现 ImageCache 接口的类,然后构造该类的对象,并且通过 setImageCache(ImageCache imageCache) 注入到ImageLoader 中,这样 ImageLoader 就实现了千变万化的缓存策略,切扩展这些缓存策略并不会导致 ImageLoader 类的修改。

    相关文章

      网友评论

          本文标题:开放 — 封闭原则

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