美文网首页Android知识Android开发Android技术知识
Android代码设计及其应用(1)-面向对象的六大原则

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

作者: Android开发哥 | 来源:发表于2017-03-10 14:54 被阅读153次

    最近在学习设计模式, 搜索大量的资料发现很多资料都是只是说明这些设计模式是怎样的, 而没有说明实际用途, 大量的资料都是重叠重复的. 虽说入门, 但是给出例子之后就没有再深入下去了. 学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();
                }
            }
        }
    }
    

    迪米特原则

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

    什么意思?

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

    总结

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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/bxzngttx.html