六大设计原则之开闭原则

  • 概念
    开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是
    封闭的。简单说就是,对于经过测试已经稳定的代码禁止修改,如果有新的需求只允许扩展原有代
    码。因为修改可能将错误引入到已经经过测试稳定的代码,破坏原有系统。当然这只是理想中的状
    态,在实际开发中,修改原有代码和扩展代码都同时存在的,我们要做的就是不断的去接近这种理
    想的状态。开闭原则是面向对象编程世界里最基础的设计原则,它指导我们如何构建一个稳定的、
    灵活的系统。
    那么我们怎么扩展代码,怎么封闭代码?还是用上一篇文章中的例子来说明。
    在单一职责里面的例子(六大设计原则之单一职责),似乎是完美的,通过内存缓存解决了每次都
    从网络加载图片的问题,但是当内存被回收或者应用重启,原来加载过的图片就消失,需要重新加
    载,这样耗流量耗时间,体验很差。于是,现在提出了新的缓存策略:用SD卡缓存。文章有点长,
    代码有点多,是为了引出问题,然后解决问题,总之专研技术需要耐心,最好自己敲一遍代码,体
    会其中的不同,通过层层优化,最后达到较理想的效果。

  • 优化之前的代码逻辑

    sd卡缓存:

package com.study.demo.designdemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Created by WKC on 2017/8/8.
 */

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

    //从缓存中获取图片
    public Bitmap get(String url) {
        return BitmapFactory.decodeFile(cacheDir + url);
    }
    //将图片缓存到内存中
    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的代码,具体代码如下

package com.study.demo.designdemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 图片加载类
 * Created by WKC on 2017/7/8.
 */

public class ImageLoader {
    private Bitmap bitmap;
    //是否使用SD卡缓存
    private boolean isUseDiskCache = false;
    //内存缓存
    private ImageCache mImageCache = new ImageCache();
    //SD卡缓存
    private DiskCache mDiskCache = new DiskCache();


    //线程池,线程数量为CUP的数量
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().
            availableProcessors());
    //UI Handler
    Handler mUiHandler = new Handler(Looper.getMainLooper());

    /**
     * 提供外部调用显示图片的函数
     *
     * @param url       图片的路径
     * @param imageview 显示图片的view
     */
    public void displayImage(final String url, final ImageView imageview) {
        bitmap = getUseDiskCache() ? 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);
            }
        });
    }

    /**
     * 在UI线程显示图片
     *
     * @param imageview
     * @param bitmap
     */
    private void updateImageview(final ImageView imageview, final Bitmap bitmap) {
        mUiHandler.post(new Runnable() {
            @Override
            public void run() {
                imageview.setImageBitmap(bitmap);
            }
        });
    }

    /**
     * 下载图片
     *
     * @param imegeUrl
     * @return
     */
    private Bitmap downloadImage(String imegeUrl) {
        Bitmap bitmap = null;
        try {
            URL url = new URL(imegeUrl);
            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }

    public boolean getUseDiskCache() {
        return isUseDiskCache;
    }

    public void setUseDiskCache(boolean useDiskCache) {
        isUseDiskCache = useDiskCache;
    }
}

通过以上代码,仅增加了DiskCache类和往imageLoader类中加入少量的代码就增加了SD卡缓存的
功能,且我们可以直接设置useDiskCache的值来决定是否使用SD卡缓存,很是方便。但是聪明的
你一定发现了上面的例子中,当用户使用了内存就不能用SD卡缓存,反之亦然。能不能再优化下,
两种缓存策略都用,即优先使用内存缓存,如果没有图片再使用SD卡缓存,如果SD卡中也没有图片
最后才从网络中获取。于是,我们新建一个双缓存类:

双缓存类

package com.study.demo.designdemo;

import android.graphics.Bitmap;

/**
 * Created by WKC on 2017/7/8.
 */

public class DoubleCache {
    //内存
    ImageCache mMemoryCache = new ImageCache();
    //SD卡
    DiskCache mDiskCache = new DiskCache();

    //先从内存中获取图片,如果没有,再从SD卡中获取
    public Bitmap get(String url) {
        Bitmap bitmap = mMemoryCache.get(url);
        if (bitmap == null) {
            bitmap = mDiskCache.get(url);
        }
        return bitmap;
    }

    //将图片缓存
    public void put(String url, Bitmap bitmap) {
        mMemoryCache.put(url, bitmap);
        mDiskCache.put(url, bitmap);
    }
}

同样,ImageLoader类也需要更新,代码如下

package com.study.demo.designdemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 图片加载类
 * Created by WKC on 2017/7/8.
 */

public class ImageLoader {
    private Bitmap bitmap;

    //内存缓存
    private ImageCache mImageCache = new ImageCache();
    //SD卡缓存
    private DiskCache mDiskCache = new DiskCache();
    //双缓存
    private DoubleCache mDoubleCache = new DoubleCache();
    //是否使用SD卡缓存
    private boolean isUseDiskCache = false;
    //是否使用双缓存
    private boolean isDoubleCache = false;

    //线程池,线程数量为CUP的数量
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().
            availableProcessors());
    //UI Handler
    Handler mUiHandler = new Handler(Looper.getMainLooper());

    /**
     * 提供外部调用显示图片的函数
     *
     * @param url       图片的路径
     * @param imageview 显示图片的view
     */
    public void displayImage(final String url, final ImageView imageview) {
        if (getDoubleCache()) {
            bitmap = mDoubleCache.get(url);
        } else if (getUseDiskCache()) {
            bitmap = mDiskCache.get(url);
        } else {
            bitmap = 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);
            }
        });
    }

    /**
     * 在UI线程显示图片
     *
     * @param imageview
     * @param bitmap
     */
    private void updateImageview(final ImageView imageview, final Bitmap bitmap) {
        mUiHandler.post(new Runnable() {
            @Override
            public void run() {
                imageview.setImageBitmap(bitmap);
            }
        });
    }

    /**
     * 下载图片
     *
     * @param imegeUrl
     * @return
     */
    private Bitmap downloadImage(String imegeUrl) {
        Bitmap bitmap = null;
        try {
            URL url = new URL(imegeUrl);
            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }

    public boolean getUseDiskCache() {
        return isUseDiskCache;
    }

    public void setUseDiskCache(boolean useDiskCache) {
        isUseDiskCache = useDiskCache;
    }

    public boolean getDoubleCache() {
        return isDoubleCache;
    }

    public void setDoubleCache(boolean doubleCache) {
        isDoubleCache = doubleCache;
    }
}

有没有发现,每当我们新增加一个需求,我们所有的类都需要改动,麻烦不说,万一有其他类正在
调用原有的代码,是不是可能会惹来“麻烦”?而且使用了很多的判断逻辑导致代码很复杂,这是
很容易出现bug的,而且是不容易找出.再者,用户不能自己实现/定义缓存注入到ImageLoader中,可
扩展性差,背离了面向对象的思想。
“软件中的对象(类。模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的,这就是开
放关闭原则。”也就是说当需求变化时,我们应该尽量通过扩展方式来实现变化,而不是通过修改
已有的代码来实现。为了实现这样的效果,我们需要对前面的代码来一次重构,对上面的代码重构
中涉及的几个类的代码都发生了不同程度的改变,所以我打算把所有的各类的代码都贴出来。

  • 优化之后的代码逻辑

我们先看下UML图,对UML图不了解的这篇文章深入浅出UML类图对你有帮助。
这里写图片描述

代码中的注释已经很详细了,就不在详细解释。

1.ImageLoader类

package com.study.demo.designdemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 图片加载类
 * Created by WKC on 2017/7/8.
 */

public class ImageLoader {
    //默认的缓存方式
    private ImageCache mImageCache = new MemoryCache();

    //线程池,线程数量为CUP的数量
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().
            availableProcessors());
    //UI Handler
    Handler mUiHandler = new Handler(Looper.getMainLooper());


    /**
     * 在UI线程显示图片
     *
     * @param imageview
     * @param bitmap
     */
    private void updateImageview(final ImageView imageview, final Bitmap bitmap) {
        mUiHandler.post(new Runnable() {
            @Override
            public void run() {
                imageview.setImageBitmap(bitmap);
            }
        });
    }

    /**
     * 注入缓存实现
     *
     * @param cache
     */
    public void setImageCache(ImageCache cache) {
        mImageCache = cache;
    }

    /**
     * 提供外部调用显示图片的函数
     *
     * @param url       图片的路径
     * @param imageview 显示图片的view
     */
    public void displayImage(final String url, final ImageView imageview) {
        Bitmap bitmap = mImageCache.get(url);
        if (bitmap != null) {
            imageview.setImageBitmap(bitmap);
            return;
        }

        submitLoadRequest(url, imageview);
    }

    /**
     * 没有缓存,则从网络中获取
     *
     * @param url
     * @param imageview
     */
    private void submitLoadRequest(final String url, final ImageView imageview) {
        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);
            }
        });
    }

    /**
     * 下载图片
     *
     * @param imegeUrl
     * @return
     */
    private Bitmap downloadImage(String imegeUrl) {
        Bitmap bitmap = null;
        try {
            URL url = new URL(imegeUrl);
            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

经过这次重构,没有了那么多的 if-else 语句了,没有了各种各样的缓存实现对象、布尔变量,代码
也清晰简单了很多。不过这里用到的ImageCache 已经不是之前的那个ImageCache类了,而是把
他提取成图片缓存接口,抽象缓存的功能。我们来看看该接口中的声明:

2.图片缓存接口

public interface ImageCache {
    void put(String url, Bitmap bitmap);

    Bitmap get(String url);
}

该接口定义了获取和缓存图片的两个函数,缓存的key是图片的url,值是图片本身。然后让内存缓
存、SD卡缓存或者

双缓存都实现该接口。
3.内存缓存

package com.study.demo.designdemo;
import android.graphics.Bitmap;
import android.util.LruCache;

/**
 * Created by WKC on 2017/7/8.
 */

public class MemoryCache implements ImageCache {
    private LruCache<String, Bitmap> mMemoryCache;

    public MemoryCache() {
        //初始化缓存
        initImageCache();
    }

    @Override
    public Bitmap get(String url) {
        return mMemoryCache.get(url);
    }

    @Override
    public void put(String url, Bitmap bitmap) {
        mMemoryCache.put(url, bitmap);
    }

    //初始化缓存内存的参数
    private void initImageCache() {
        //计算可使用的最大内存
        final long maxMemory = Runtime.getRuntime().maxMemory() / 1024;
        //取四分之一的可用内存作为缓存
        final int cacheSize = (int) (maxMemory / 4);
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
    }
}

4.SD卡缓存

package com.study.demo.designdemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Created by WKC on 2017/7/8.
 */

public class DiskCache implements ImageCache {
    private static String cacheDir = "sdcard/cache/";
    /**
     * 从SD卡缓存中获取图片
     * @param url
     * @return
     */
    @Override
    public Bitmap get(String url) {
        return BitmapFactory.decodeFile(cacheDir + url);
    }

    @Override
    public void put(String url, Bitmap bitmap) {
        FileOutputStream fileOutputStream = null;
        try {
            //从内存中写入SD卡
            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();
                }
            }
        }
    }
}

5.双缓存

package com.study.demo.designdemo;

import android.graphics.Bitmap;

/**
 * Created by WKC on 2017/7/8.
 */

public class DoubleCache implements ImageCache {
    //内存
    private ImageCache mMemoryCache = new MemoryCache();
    //SD卡
    private ImageCache mDiskCache = new DiskCache();

    @Override
    public Bitmap get(String url) {
        Bitmap bitmap = mMemoryCache.get(url);
        if (bitmap == null) {
            bitmap = mDiskCache.get(url);
        }
        return bitmap;
    }

    @Override
    public void put(String url, Bitmap bitmap) {
        mMemoryCache.put(url, bitmap);
        mDiskCache.put(url, bitmap);
    }
}

6.最后是调用
细心的读者可能注意到了,在ImageLoader中增加了SetImageCache(ImageCache cache)函
数,用户可以通过该函数设置缓存实现,不再需要去更改Imageloader函数中的代码了,也就
是常说的依赖注入。这样也就达到了我们的最初目的。
我们来看下用户如何设置缓存的

 imageView = (ImageView) findViewById(R.id.imageView);
        //使用内存缓存策略
        imageLoader.setImageCache(new MemoryCache());
        //使用SD卡缓存策略
        imageLoader.setImageCache(new DiskCache());
        //使用双缓存策略
        imageLoader.setImageCache(new DoubleCache());
        //使用自定义的缓存
        imageLoader.setImageCache(new ImageCache() {
            @Override
            public Bitmap get(String url) {
                //获取图片,返回Bitmap对象
                return null;
            }

            @Override
            public void put(String url, Bitmap bitmap) {
//                缓存图片
            }
        });
        imageLoader.displayImage(url,imageView);

最后来下总结:
1.需求的变更
内存缓存实现图片缓存–>磁盘缓存实现图片缓存–>内存和磁盘的双缓存实现图片缓存。
2.如何优化
从开始的每次需求的变更都需要去更改原先的代码,而且代码中有了不少的逻辑判断,容易
出现失误。–>我们定义了一个规则(ImageCache接口),当我们的需求变化时,只需要更改
实现这个接口,并把它注入缓存类(ImageLoader类),就能在不改变原有代码的条件下实
现了我们新的需求,也没有了那么多的逻辑判断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值