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();
            }
        }
    }
}

迪米特原则

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

什么意思?

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

总结

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

    原文作者:架构
    原文地址: https://www.jianshu.com/p/e3fcb2f7a9c3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞