Android设计模式之开闭原则

一、定义

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

二、场景运用

回顾下 Android设计模式之单一职责原则 里图片加载器的代码,通过内存缓存确实解决了每次从网络加载图片的问题,但是当应用重启后,原来加载过的图片就会丢失,又要重新下载,导致加载缓慢,而且还耗费流量。

此时小明考虑引入 SDCard 缓存,这样下载过的图片就会缓存到本地,即使重启应用也不需要重新下载了。

于是乎,小明开始了 coding

/**
 * 图片加载器
 */
public class ImageLoader {

    //图片缓存
    private final ImageLruCache imageLruCache = new ImageLruCache();
    //SDCard缓存
    private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();
    //线程池,线程数量为Cpu数量
    private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    //Ui Handler
    private final Handler uiHandler = new Handler(Looper.getMainLooper());
    //是否使用SDCard缓存
    private boolean useSDCardCache = false;

    /**
     * 设置是否使用SDCard缓存
     *
     * @param useSDCardCache
     */
    public void setUseSDCardCache(boolean useSDCardCache) {
        this.useSDCardCache = useSDCardCache;
    }

    /**
     * 展示图片
     *
     * @param imageUrl
     * @param imageView
     */
    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = useSDCardCache ? imageSDCardCache.get(imageView.getContext(), imageUrl) : imageLruCache.get(imageUrl);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(imageUrl);
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = downloadImage(imageUrl);
                if (bitmap != null) {
                    if (imageView.getTag().equals(imageUrl)) {
                        postToImageView(imageView, bitmap);
                    }
                    if (useSDCardCache) {
                        imageSDCardCache.put(imageView.getContext(), imageUrl, bitmap);
                    } else {
                        imageLruCache.put(imageUrl, bitmap);
                    }                                   
                }
            }
        });
    }

    /**
     * 更新到ImageView上
     *
     * @param imageView
     * @param bitmap
     */
    private void postToImageView(ImageView imageView, Bitmap bitmap) {
        uiHandler.post(new Runnable() {
            @Override
            public void run() {
                imageView.setImageBitmap(bitmap);
            }
        });
    }

    /**
     * 下载图片
     *
     * @param imageUrl
     * @return
     */
    private Bitmap downloadImage(String imageUrl) {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(imageUrl);
            connection = (HttpURLConnection) url.openConnection();
            if (connection.getResponseCode() == 200) {
                //忽略图片压缩过程...
                return BitmapFactory.decodeStream(connection.getInputStream());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
        return null;
    }
}

/**
 * 图片缓存
 */
public class ImageLruCache {

    //图片缓存
    private final LruCache<String, Bitmap> imageLruCache;

    public ImageLruCache() {
        //可使用最大内存
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        //取1/4可用内存作为图片缓存
        final int imageLruCacheSize = maxMemory / 4;
        imageLruCache = new LruCache<String, Bitmap>(imageLruCacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

    /**
     * 存入图片
     *
     * @param imageUrl
     * @param bitmap
     */
    public void put(String imageUrl, Bitmap bitmap) {
        imageLruCache.put(imageUrl, bitmap);
    }

    /**
     * 取出图片
     *
     * @param imageUrl
     * @return
     */
    public Bitmap get(String imageUrl) {
        return imageLruCache.get(imageUrl);
    }
}

/**
 * SDCard缓存
 */
public class ImageSDCardCache {

    public Bitmap get(Context context, String imageUrl) {
        return BitmapFactory.decodeFile(context.getCacheDir().getAbsolutePath() + imageUrl);
    }

    public void put(Context context, String imageUrl, Bitmap bitmap) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(context.getCacheDir().getAbsolutePath() + imageUrl);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
复制代码

通过 setUseSDCardCache 方法就可以让用户设置不同的缓存,小明很满意,于是提交给主管做代码审查。

主管:思路是对的,但还是有问题,就是使用内存缓存时用户就不能使用 SDCard 缓存。用户需要的是两种策略的综合,首先优先使用内存缓存,如果内存缓存没有图片再使用 SDCard 缓存,如果 SDCard 缓存也没有图片,最后才从网络上获取。

小明(脑子仿佛如梦初醒):我马上优化下。

于是,小明开始了 coding

/**
 * 图片加载器
 */
public class ImageLoader {

    //图片缓存
    private final ImageLruCache imageLruCache = new ImageLruCache();
    //SDCard缓存
    private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();
    //双缓存
    private final DoubleCache doubleCache = new DoubleCache();
    //线程池,线程数量为Cpu数量
    private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    //Ui Handler
    private final Handler uiHandler = new Handler(Looper.getMainLooper());
    //是否使用SDCard缓存
    private boolean useSDCardCache = false;
    //是否使用双缓存
    private boolean useDoubleCache = false;

    /**
     * 设置是否使用SDCard缓存
     *
     * @param useSDCardCache
     */
    public void setUseSDCardCache(boolean useSDCardCache) {
        this.useSDCardCache = useSDCardCache;
    }

    /**
     * 是否使用双缓存
     *
     * @param useDoubleCache
     */
    public void setUseDoubleCache(boolean useDoubleCache) {
        this.useDoubleCache = useDoubleCache;
    }

    /**
     * 展示图片
     *
     * @param imageUrl
     * @param imageView
     */
    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = null;
        if (useDoubleCache) {
            bitmap = doubleCache.get(imageView.getContext(), imageUrl);
        } else if (useSDCardCache) {
            bitmap = imageSDCardCache.get(imageView.getContext(), imageUrl);
        } else {
            bitmap = imageLruCache.get(imageUrl);
        }
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(imageUrl);
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = downloadImage(imageUrl);
                if (bitmap != null) {
                    if (imageView.getTag().equals(imageUrl)) {
                        postToImageView(imageView, bitmap);
                    }
                    if (useDoubleCache) {
                        doubleCache.put(imageView.getContext(), imageUrl, bitmap);
                    } else if (useSDCardCache) {
                        imageSDCardCache.put(imageView.getContext(), imageUrl, bitmap);
                    } else {
                        imageLruCache.put(imageUrl, bitmap);
                    }
                }
            }
        });
    }

    /**
     * 更新到ImageView上
     *
     * @param imageView
     * @param bitmap
     */
    private void postToImageView(ImageView imageView, Bitmap bitmap) {
        uiHandler.post(new Runnable() {
            @Override
            public void run() {
                imageView.setImageBitmap(bitmap);
            }
        });
    }

    /**
     * 下载图片
     *
     * @param imageUrl
     * @return
     */
    private Bitmap downloadImage(String imageUrl) {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(imageUrl);
            connection = (HttpURLConnection) url.openConnection();
            if (connection.getResponseCode() == 200) {
                //忽略图片压缩过程...
                return BitmapFactory.decodeStream(connection.getInputStream());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
        return null;
    }
}

/**
 * 图片缓存
 */
public class ImageLruCache {

    //图片缓存
    private final LruCache<String, Bitmap> imageLruCache;

    public ImageLruCache() {
        //可使用最大内存
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        //取1/4可用内存作为图片缓存
        final int imageLruCacheSize = maxMemory / 4;
        imageLruCache = new LruCache<String, Bitmap>(imageLruCacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

    /**
     * 存入图片
     *
     * @param imageUrl
     * @param bitmap
     */
    public void put(String imageUrl, Bitmap bitmap) {
        imageLruCache.put(imageUrl, bitmap);
    }

    /**
     * 取出图片
     *
     * @param imageUrl
     * @return
     */
    public Bitmap get(String imageUrl) {
        return imageLruCache.get(imageUrl);
    }
}

/**
 * SDCard缓存
 */
public class ImageSDCardCache {

    public Bitmap get(Context context, String imageUrl) {
        return BitmapFactory.decodeFile(context.getCacheDir().getAbsolutePath() + imageUrl);
    }

    public void put(Context context, String imageUrl, Bitmap bitmap) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(context.getCacheDir().getAbsolutePath() + imageUrl);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

/**
 * 双缓存
 */
public class DoubleCache {

    //图片缓存
    private final ImageLruCache imageLruCache = new ImageLruCache();
    //SDCard缓存
    private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();

    public Bitmap get(Context context, String imageUrl) {
        Bitmap bitmap = imageLruCache.get(imageUrl);
        if (bitmap == null) {
            bitmap = imageSDCardCache.get(context, imageUrl);
        }
        return bitmap;
    }

    public void put(Context context, String imageUrl, Bitmap bitmap) {
        imageLruCache.put(imageUrl, bitmap);
        imageSDCardCache.put(context, imageUrl, bitmap);
    }
}
复制代码

小明通过几处修改就完成了这功能,顿时感到自己 Android 开发得心应手了。

主管:你每次加入新的缓存都要修改原来的代码,这样很容易引入 Bug,而且代码逻辑也变复杂了。按照这种设计,用户也不能完成自定义缓存。

小明听得云里雾里的,主管只能亲自操刀了。

/**
 * 图片加载器
 */
public class ImageLoader {

    //图片缓存
    private IImageCache imageCache = new ImageLruCache();
    //线程池,线程数量为Cpu数量
    private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    //Ui Handler
    private final Handler uiHandler = new Handler(Looper.getMainLooper());

    /**
     * 设置图片缓存
     *
     * @param imageCache
     */
    public void setImageCache(IImageCache imageCache) {
        this.imageCache = imageCache;
    }

    /**
     * 展示图片
     *
     * @param imageUrl
     * @param imageView
     */
    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = imageCache.get(imageView.getContext(), imageUrl);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(imageUrl);
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = downloadImage(imageUrl);
                if (bitmap != null) {
                    if (imageView.getTag().equals(imageUrl)) {
                        postToImageView(imageView, bitmap);
                    }
                    imageCache.put(imageView.getContext(), imageUrl, bitmap);
                }
            }
        });
    }

    /**
     * 更新到ImageView上
     *
     * @param imageView
     * @param bitmap
     */
    private void postToImageView(ImageView imageView, Bitmap bitmap) {
        uiHandler.post(new Runnable() {
            @Override
            public void run() {
                imageView.setImageBitmap(bitmap);
            }
        });
    }

    /**
     * 下载图片
     *
     * @param imageUrl
     * @return
     */
    private Bitmap downloadImage(String imageUrl) {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(imageUrl);
            connection = (HttpURLConnection) url.openConnection();
            if (connection.getResponseCode() == 200) {
                //忽略图片压缩过程...
                return BitmapFactory.decodeStream(connection.getInputStream());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
        return null;
    }
}

/**
 * 抽象缓存接口
 */
public interface IImageCache {

    /**
     * 存入图片
     *
     * @param context
     * @param imageUrl
     * @return
     */
    Bitmap get(Context context, String imageUrl);

    /**
     * 取出图片
     *
     * @param context
     * @param imageUrl
     * @param bitmap
     */
    void put(Context context, String imageUrl, Bitmap bitmap);
}

/**
 * 图片缓存
 */
public class ImageLruCache implements IImageCache {

    //图片缓存
    private final LruCache<String, Bitmap> imageLruCache;

    public ImageLruCache() {
        //可使用最大内存
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        //取1/4可用内存作为图片缓存
        final int imageLruCacheSize = maxMemory / 4;
        imageLruCache = new LruCache<String, Bitmap>(imageLruCacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

    @Override
    public Bitmap get(Context context, String imageUrl) {
        return imageLruCache.get(imageUrl);
    }

    @Override
    public void put(Context context, String imageUrl, Bitmap bitmap) {
        imageLruCache.put(imageUrl, bitmap);
    }
}

/**
 * SDCard缓存
 */
public class ImageSDCardCache implements IImageCache{

    @Override
    public Bitmap get(Context context, String imageUrl) {
        return BitmapFactory.decodeFile(context.getCacheDir().getAbsolutePath() + imageUrl);
    }

    @Override
    public void put(Context context, String imageUrl, Bitmap bitmap) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(context.getCacheDir().getAbsolutePath() + imageUrl);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

/**
 * 双缓存
 */
public class DoubleCache implements IImageCache {

    //图片缓存
    private final ImageLruCache imageLruCache = new ImageLruCache();
    //SDCard缓存
    private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();

    @Override
    public Bitmap get(Context context, String imageUrl) {
        Bitmap bitmap = imageLruCache.get(context, imageUrl);
        if (bitmap == null) {
            bitmap = imageSDCardCache.get(context, imageUrl);
        }
        return bitmap;
    }

    @Override
    public void put(Context context, String imageUrl, Bitmap bitmap) {
        imageLruCache.put(context, imageUrl, bitmap);
        imageSDCardCache.put(context, imageUrl, bitmap);
    }
}
复制代码

上述代码中,用户通过 setImageCache 方法就可以注入不同的缓存实现,因为所有的缓存都有一个特点,那就是实现了 IImageCache 接口,这样就使得 ImageLoader 的扩展性更强了。

小明此时茅塞顿开。

三、总结

当软件需要变化时,应该尽量通过扩展的方式来实现变化,而不是通过修改已有代码来实现。“应该尽量”这四字说明并不是不能修改原始类,当嗅到原来代码“腐化气味”时,应该尽早重构。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享