Android代码设计及其应用(1)-面向对象的六大原则

最近在学习设计模式, 搜索大量的资料发现很多资料都是只是说明这些设计模式是怎样的, 而没有说明实际用途, 大量的资料都是重叠重复的. 虽说入门, 但是给出例子之后就没有再深入下去了. 学Android开发的, 很多时候看完用Java写的设计模式代码, 但是却不知道怎么应用到实际的项目开发中去. 所以打算根据自己的�一些拙见, 能把经常使用的设计方法写成文章互相交流.

从ImageLoader说起

如果需要写一个ImageLoader,那么一般代码就像下面那样

public class ImageLoader {

    private static final int SHOW_IMAGE = 100;

    private static final ImageLoader mInstance = new ImageLoader();
    private ExecutorService mExecutors;
    private LruCache<String, Bitmap> mCache;
    private Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            if (msg.what == SHOW_IMAGE) {
                ImageHolder holder = (ImageHolder) msg.obj;
                if (holder != null && holder.imageView != null && holder.bitmap != null && holder.url != null
                        && holder.url.equals(holder.imageView.getTag())) {
                    holder.imageView.setImageBitmap(holder.bitmap);
                }
            }
        };
    };

    /**
     * 私有化构造函数并初始化
     */
    private ImageLoader() {
        mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        mCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 4)) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

    /**
     * 图片显示
     * 
     * @param imageView
     * @param url
     */
    public void displayImage(final ImageView imageView, final String url) {
        imageView.setTag(url);
        Bitmap cacheBitmap = mCache.get(url);
        if (cacheBitmap != null) {
            imageView.setImageBitmap(cacheBitmap);
            return;
        }
        final ImageHolder holder = new ImageHolder();
        holder.imageView = imageView;
        holder.url = url;
        mExecutors.submit(new Runnable() {

            @Override
            public void run() {
                Bitmap remoteBitmap = downloadImage(holder.url);
                mCache.put(holder.url, remoteBitmap);
                holder.bitmap = remoteBitmap;
                Message message = mHandler.obtainMessage(SHOW_IMAGE);
                message.obj = holder;
                mHandler.sendMessage(message);
            }
        });

    }

    /**
     * 下载图片
     * 
     * @param url
     * @return
     */
    private Bitmap downloadImage(String url) {
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeStream(new URL(url).openStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }

    /**
     * 获取实例
     * 
     * @return
     */
    public static ImageLoader getInstance() {
        return mInstance;
    }

    /**
     * 显示图片消息数据传输对象
     * 
     * @author August
     *
     */
    static final class ImageHolder {
        ImageView imageView;
        Bitmap bitmap;
        String url;
    }
}

好, 至此一个ImageLoader就已经写完了.我们下面根据面向对象的六大原则对它进行改造

单一职责原则

书面语就两句重要的话:

  • 就一个类而言,应该仅有一个引起它变化的原因

  • 一个类中应该是一组相关性很高的函数和数据的封装

上面我们把ImageLoader的各部分都卸载一个类中, 所以已经违背了该原则, 我们应该尽量地去简化每个类的工作量. 把相关度不高的部分分离出去. 于是我们就有了下面的改造.

Downloader

/**
 * 下载网络图片
 * 
 * @author August
 *
 */
public class Downloader {
    public Bitmap downloadImage(String url) {
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeStream(new URL(url).openStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

ImageCache

/**
 * 图片缓存类
 * @author August
 *
 */
public class ImageCache {
    private LruCache<String, Bitmap> mCache;

    public ImageCache() {
        mCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 4)) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

    public Bitmap get(String url) {
        return mCache.get(url);
    }

    public void put(String url, Bitmap bitmap) {
        mCache.put(url, bitmap);
    }
}

ImageHolder

/**
 * 显示图片消息的数据传输对象
 * 
 * @author August
 *
 */
public class ImageHolder {
    public ImageView imageView;
    public Bitmap bitmap;
    public String url;

    public boolean isVerify() {
        return imageView != null && bitmap != null && url != null && url.equals(imageView.getTag());
    }

    public void showImage() {
        if (isVerify()) {
            imageView.setImageBitmap(bitmap);
        }
    }

}

ImageHandler

/**
 * 消息处理类
 * @author August
 *
 */
public class ImageHandler extends Handler {
    public static final int SHOW_IMAGE = 100;

    public void handleMessage(android.os.Message msg) {
        if (msg.what == SHOW_IMAGE) {
            ImageHolder holder = (ImageHolder) msg.obj;
            if (holder != null) {
                holder.showImage();
            }
        }
    };
}

ImageLoader

public class ImageLoader {

    private static final ImageLoader mInstance = new ImageLoader();
    private ExecutorService mExecutors;
    private ImageCache mCache = new ImageCache();
    private Downloader mDownloader = new Downloader();
    private Handler mHandler = new ImageHandler();

    /**
     * 私有化构造函数并初始化
     */
    private ImageLoader() {
        mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    /**
     * 图片显示
     * 
     * @param imageView
     * @param url
     */
    public void displayImage(final ImageView imageView, final String url) {
        imageView.setTag(url);
        Bitmap cacheBitmap = mCache.get(url);
        if (cacheBitmap != null) {
            imageView.setImageBitmap(cacheBitmap);
            return;
        }
        final ImageHolder holder = new ImageHolder();
        holder.imageView = imageView;
        holder.url = url;
        mExecutors.submit(new Runnable() {

            @Override
            public void run() {
                Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
                mCache.put(holder.url, remoteBitmap);
                holder.bitmap = remoteBitmap;
                Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
                message.obj = holder;
                mHandler.sendMessage(message);
            }
        });

    }

    /**
     * 获取实例
     * 
     * @return
     */
    public static ImageLoader getInstance() {
        return mInstance;
    }
}

可以看到现在我们的ImageLoader已经是有模有样地分开了几个类.

开闭原则

也是两句话作总结

  • 软件中的对象(类 模块 函数等)应该对于扩展是开放的, 但是对于修改是封闭的.

  • 程序一旦开发完成, 程序中的一个类的实现只应该因错误而被修改, 新的或者改变的特性应该通过新建不同的类实现, 新建的类可以通过集成的方式来重用原来的代码.

上面的ImageLoader只在内存中缓存, 后来发现一级的缓存是行不通的. 因为Bitmap占用的内存太, 很容易被回收. 所以我们需要使用磁盘缓存. 然后我们增加DiskCache类并且修改ImageLoader.

DiskCache

public class DiskCache {

    private File mDir;

    public DiskCache() {
        mDir = new File(Environment.getExternalStorageDirectory(), "cache");
        if (!mDir.exists()) {
            mDir.mkdir();
        }
    }

    public void put(String url, Bitmap bitmap) {
        OutputStream os = null;
        try {
            os = new FileOutputStream(new File(mDir, getDiskName(url)));
            bitmap.compress(CompressFormat.JPEG, 100, os);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        bitmap.compress(CompressFormat.JPEG, 100, os);
    }

    public Bitmap get(String url) {

        File file = new File(mDir, getDiskName(url));
        Bitmap bitmap = null;
        if (file.exists()) {
            bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
        }
        return bitmap;
    }

    private String getDiskName(String url) {
        return Base64.encodeToString(url.getBytes(), Base64.DEFAULT);
    }

}

ImageLoader

public class ImageLoader {

    private static final ImageLoader mInstance = new ImageLoader();
    private ExecutorService mExecutors;
    private DiskCache mCache = new DiskCache();
    private Downloader mDownloader = new Downloader();
    private Handler mHandler = new ImageHandler();

    /**
     * 私有化构造函数并初始化
     */
    private ImageLoader() {
        mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    /**
     * 图片显示
     * 
     * @param imageView
     * @param url
     */
    public void displayImage(final ImageView imageView, final String url) {
        imageView.setTag(url);
        Bitmap cacheBitmap = mCache.get(url);
        if (cacheBitmap != null) {
            imageView.setImageBitmap(cacheBitmap);
            return;
        }
        final ImageHolder holder = new ImageHolder();
        holder.imageView = imageView;
        holder.url = url;
        mExecutors.submit(new Runnable() {

            @Override
            public void run() {
                Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
                mCache.put(holder.url, remoteBitmap);
                holder.bitmap = remoteBitmap;
                Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
                message.obj = holder;
                mHandler.sendMessage(message);
            }
        });

    }

    /**
     * 获取实例
     * 
     * @return
     */
    public static ImageLoader getInstance() {
        return mInstance;
    }
}

什么??? 只有磁盘缓存又不够快??? 要做二级缓存???? 代码又要改...也不怎么难, 我们添加一个DoubleCache的类

DoubleCache

/**
 * 二级缓存类
 * 
 * @author August
 *
 */
public class DoubleCache {
    private ImageCache mImageCache = new ImageCache();
    private DiskCache mDiskCache = new DiskCache();

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

    public void put(String url, Bitmap bitmap) {
        mImageCache.put(url, bitmap);
        mDiskCache.put(url, bitmap);
    }
}

ImageLoader

public class ImageLoader {

    private static final ImageLoader mInstance = new ImageLoader();
    private ExecutorService mExecutors;
    private DoubleCache mCache = new DoubleCache();
    private Downloader mDownloader = new Downloader();
    private Handler mHandler = new ImageHandler();

    /**
     * 私有化构造函数并初始化
     */
    private ImageLoader() {
        mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    /**
     * 图片显示
     * 
     * @param imageView
     * @param url
     */
    public void displayImage(final ImageView imageView, final String url) {
        imageView.setTag(url);
        Bitmap cacheBitmap = mCache.get(url);
        if (cacheBitmap != null) {
            imageView.setImageBitmap(cacheBitmap);
            return;
        }
        final ImageHolder holder = new ImageHolder();
        holder.imageView = imageView;
        holder.url = url;
        mExecutors.submit(new Runnable() {

            @Override
            public void run() {
                Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
                mCache.put(holder.url, remoteBitmap);
                holder.bitmap = remoteBitmap;
                Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
                message.obj = holder;
                mHandler.sendMessage(message);
            }
        });

    }

    /**
     * 获取实例
     * 
     * @return
     */
    public static ImageLoader getInstance() {
        return mInstance;
    }
}

虽然说是完成了需求, 但是我们做了一个非常蠢的事情,就是去修改了ImageLoader的代码...这明显是违背了开闭原则的.下面介绍一下里氏替换原则和依赖倒置原则.

里氏替换原则

还是两句话

  • 所有引用基类的地方必须能透明地使用其子类的对象

  • 里氏替换原则的核心原理是抽象, 抽象又依赖于继承这个特性, 通过建立抽象, 通过抽象建立规范, 具体的实现在运行时替换掉抽象, 保证系统的扩展性和灵活性.

优点

  • 代码重用, 减少创建类的成本, 每个子类都有父类的方法和属性

  • 子类与父类基本相似, 但是又有所区别

  • 提高代码的可扩展性

依赖倒置原则

依赖倒置原则只带一种特定的解耦形式, 使得高层次的模块不依赖于低层次的模块.

关键点

  • 高层模块不应该依赖底层模块, 两者都应该依赖抽象

  • 抽象不应该依赖细节

  • 细节应该依赖抽象

在Java中, 抽象就是借口或者抽象类, 细节就是实现类

回到ImageLoader中, 上面的图片缓存就是直接依赖于缓存的具体实现. 修改后我们可以依赖起父类或者接口. 但是内存缓存和磁盘缓存的复用代码几乎没有, 所以我们选择依赖接口. 那么我们就应该设计成下面那样.

IImageCache

/**
 * 抽象的缓存接口
 * 
 * @author August
 *
 */
public interface IImageCache {
    public Bitmap get(String url);

    public void put(String url, Bitmap bitmap);
}

MemoryCache

/**
 * 内存图片缓存类
 * 
 * @author August
 *
 */
public class MemoryCache implements IImageCache {
    private LruCache<String, Bitmap> mCache;

    public MemoryCache() {
        mCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 4)) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

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

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

DiskCache

/**
 * 磁盘缓存
 * @author August
 *
 */
public class DiskCache implements IImageCache {

    private File mDir;

    public DiskCache() {
        mDir = new File(Environment.getExternalStorageDirectory(), "cache");
        if (!mDir.exists()) {
            mDir.mkdir();
        }
    }

    @Override
    public void put(String url, Bitmap bitmap) {
        OutputStream os = null;
        try {
            os = new FileOutputStream(new File(mDir, getDiskName(url)));
            bitmap.compress(CompressFormat.JPEG, 100, os);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        bitmap.compress(CompressFormat.JPEG, 100, os);
    }

    @Override
    public Bitmap get(String url) {
        File file = new File(mDir, getDiskName(url));
        Bitmap bitmap = null;
        if (file.exists()) {
            bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
        }
        return bitmap;
    }

    private String getDiskName(String url) {
        return Base64.encodeToString(url.getBytes(), Base64.DEFAULT);
    }

}

ImageLoader

public class ImageLoader {

    private static final ImageLoader mInstance = new ImageLoader();
    private ExecutorService mExecutors;
    private IImageCache mCache = new MemoryCache();
    private Downloader mDownloader = new Downloader();
    private Handler mHandler = new ImageHandler();

    /**
     * 私有化构造函数并初始化
     */
    private ImageLoader() {
        mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    /**
     * 图片显示
     * 
     * @param imageView
     * @param url
     */
    public void displayImage(final ImageView imageView, final String url) {
        imageView.setTag(url);
        Bitmap cacheBitmap = mCache.get(url);
        if (cacheBitmap != null) {
            imageView.setImageBitmap(cacheBitmap);
            return;
        }
        final ImageHolder holder = new ImageHolder();
        holder.imageView = imageView;
        holder.url = url;
        mExecutors.submit(new Runnable() {

            @Override
            public void run() {
                Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
                mCache.put(holder.url, remoteBitmap);
                holder.bitmap = remoteBitmap;
                Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
                message.obj = holder;
                mHandler.sendMessage(message);
            }
        });

    }

    /**
     * 设置缓存类型
     * 
     * @param cache
     */
    public void setImageCache(IImageCache cache) {
        mCache = cache;
    }

    /**
     * 获取实例
     * 
     * @return
     */
    public static ImageLoader getInstance() {
        return mInstance;
    }
}

可以看到上面的ImageLoader直接依赖于Cache的抽象, 即使后面扩展的时候需要加入其它类型的缓存, 开发者只需要关注IImageCache这个接口, 而对ImageLoader不需要有任何研究. 类似的还有Downloader的实现, 可以修改其打开的方式.

接口隔离原则

  • 客户端不应该依赖它不需要的接口

  • 类间的依赖关系应该建立在最小的接口上, 接口隔离原则就是将非常庞大, 臃肿的类拆分成更小的和更具体的接口.

例如上面的

OutputStream os = null;
        try {
            os = new FileOutputStream(new File(mDir, getDiskName(url)));
            bitmap.compress(CompressFormat.JPEG, 100, os);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

其中很多代码是没用的, 但是异常我们必须要去捕获, 这样我们是不是就可以写一个工具类去关闭文件呢? 于是有了下面的CloseUtilClose

CloseUtil

/**
 * 关闭流的工具类
 * 
 * @author August
 *
 */
public class CloseUtil {
    public static void close(OutputStream os) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
OutputStream os = null;
        try {
            os = new FileOutputStream(new File(mDir, getDiskName(url)));
            bitmap.compress(CompressFormat.JPEG, 100, os);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            CloseUtil.close(os);
        }

代码立刻精简了不少, 但是问题来了. InputStream也要有这样的方法啊, 那么我们是不是又要重载一个方法, 参数为InputStream..
.

这时候根据接口隔离原则, 我们应该把这种依赖关系建立在最小的接口上. 对于上面的情况, 抛出异常的是Closeable的close方法. 所以我们只要处理这种参数的就可以了, 下面的就通用了.

/**
 * 关闭流的工具类
 * 
 * @author August
 *
 */
public class CloseUtil {

    public static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

迪米特原则

  • 一个类应该对其他对象有最少的了解

什么意思?

想想, 一个类对其他对象有最少的了解, 说明了彼此间的依赖关系不是太强, 那么对于类与类之间的耦合性就减少了. 当一个类修改的时候, 对另一个类的影响就少了. 这就是各种设计和模式的目的.

总结

之前看到过一句话架构是为了妥协客观的不足, 而设计模式是为了妥协主观上的不足. 后面文章提到的设计模式, 都是为了规范开发人员的合作. 也是围绕着上面的六大原则进行的.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容