开放 — 封闭原则

在软件设计模式中,这种不能修改,但可以扩展的思想是重要的一种设计原则,是开放—封闭原则(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 mMemoryCache;

    public MemoryCache() {
        //初始化LRU缓存
        // 计算可使用的最大内存
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

        // 取4分之一的可用内存作为缓存
        final int cacheSize = maxMemory / 4;
        mMemoryCache = new LruCache(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 类的修改。

你可能感兴趣的:(开放 — 封闭原则)