结合Android源码和图片加载的例子,介绍设计模式的同时,在例子中实际运用,更易于读者对设计模式的理解和使用。

本篇博客结合书中图片加载的例子和自己对知识点的理解,侧重记录下设计模式的使用,原理部分略过。

第一章 走向灵活软件之路——面向对象的六大原则

1.1 优化代码的第一步——单一职责原则

    个人理解:“核心思想就是类的抽象和封装,将相同功能的代码封装在一个类中,不同功能的相互调用通过引用类对象方式实现"

    下面引用书中的例子,对比下

    这个是书中的第一个版本,相信大家能发现很多问题,比如每次都创建线程池和缓存,这些后面会进行优化,这块要说的是缓存的处理是一个独立的功能,应该抽象封装到一个类中。同样,下载功能也是如此。

public class ImageLoader {

    private final int BYTE_UNIT = 1024;

    // 图片缓存
    private LruCache<String, Bitmap> mImageCache;
    // 线程池,线程数量为CPU的数量
    private ExecutorService mExecutorService;

    public ImageLoader() {
        initImageCache();
        mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    private void initImageCache() {
        // 计算可使用的最大内存
        final long maxMemory = Runtime.getRuntime().maxMemory() / BYTE_UNIT;
        final int memorySize = (int) maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(memorySize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    // API19
                    return value.getAllocationByteCount() / BYTE_UNIT;
                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR_MR1) {
                    // API12
                    return value.getByteCount() / 1024;
                }
                // Bitmap所占用的内存空间数等于Bitmap的每一行所占用的空间数乘以Bitmap的行数
                return value.getRowBytes() * value.getHeight() / BYTE_UNIT;
            }
        };
    }

    /**
     * 加载图片
     * @param url
     * @param imageView
     */
    public void displayImage(final String url, final ImageView imageView) {
        // 从缓存加载
        Bitmap 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)) {
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(url, bitmap);
            }
        });
    }
    
    private Bitmap downloadImage(String url) {
        Bitmap bitmap = null;
        HttpURLConnection connection = null;
        try {
            connection = (HttpURLConnection) new URL(url).openConnection();
            bitmap = BitmapFactory.decodeStream(connection.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
        return bitmap;
    }
}

    书中在本节只对缓存功能做了封装,我们来看下封装后的代码。

public class ImageCache {
    private final int BYTE_UNIT = 1024;

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

    public ImageCache() {
        initImageCache();
    }

    private void initImageCache() {
        // 计算可使用的最大内存
        final long maxMemory = Runtime.getRuntime().maxMemory() / BYTE_UNIT;
        final int memorySize = (int) maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(memorySize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    // API19
                    return value.getAllocationByteCount() / BYTE_UNIT;
                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR_MR1) {
                    // API12
                    return value.getByteCount() / 1024;
                }
                // Bitmap所占用的内存空间数等于Bitmap的每一行所占用的空间数乘以Bitmap的行数
                return value.getRowBytes() * value.getHeight() / BYTE_UNIT;
            }
        };
    }

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

    public void put(String url, Bitmap bitmap) {
        mImageCache.put(url, bitmap);
    }
}
public class ImageLoader {
    // 封装的缓存类
    private ImageCache mImageCache = new ImageCache();
    // 线程池,线程数量为CPU的数量
    private ExecutorService mExecutorService;
    public ImageLoader() {
        mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    /**
     * 加载图片
     * @param url
     * @param imageView
     */
    public void displayImage(final String url, final ImageView imageView) {
        // 从缓存加载
        Bitmap 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)) {
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(url, bitmap);
            }
        });
    }

    private Bitmap downloadImage(String url) {
        ...................
    }
}

1.2 让程序更稳定、更灵活——开闭原则

个人理解:“就是在设计类时,尽量考虑其扩展性,保证在有需求变更时,可以不修改原有代码。一般是通过面向接口,通过多态方式实现”。

软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的,这就是开放——关闭原则。也就是说,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。

上个例子中,图片缓存我们使用的ImageCache,主要是内存缓存。

这种问题就是,如果使用者想添加磁盘缓存,则需要更改现有代码,不易于扩展。

改成下面的方式,通过定义接口,调用setImageCache方式实现开发者扩展。

public interface ImageCache {
    void put(String key, Bitmap bitmap);
    Bitmap get(String key);
}
public class MemoryCache implements ImageCache {
    // 图片缓存
    private LruCache<String, Bitmap> mImageCache;

    ..............

    @Override
    public Bitmap get(String url) {
        return mImageCache.get(url);
    }
    @Override
    public void put(String url, Bitmap bitmap) {
        mImageCache.put(url, bitmap);
    }
}
public class DiskCache implements ImageCache {
    @Override
    public void put(String key, Bitmap bitmap) {
    }
    @Override
    public Bitmap get(String key) {
        return null;
    }
}
public class ImageLoader {

    // 封装的缓存类
    private ImageCache mImageCache = new MemoryCache();
    // 线程池,线程数量为CPU的数量
    private ExecutorService mExecutorService;

    public ImageLoader() {
        mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    public void setImageCache(ImageCache cache) {
        this.mImageCache = cache;
    }

    ......................
}

1.3 构建扩展性更好的系统——里氏替换原则

咱们直接来看第二种定义:所有引用基类的地方必须能透明的使用其子类的对象。

面向对象的语言的三大特点是继承、封装、多态,里氏替换原则就是依赖于继承、多态这两大特性。

1.4 让项目拥有变化的能力——依赖倒置原则

Java语言中的表现就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。其实就是面向接口/抽象编程。

1.5 系统有更高的灵活性——接口隔离原则

定义是:客户端不应该依赖它不需要的接口。接口隔离原则将非常庞大、臃肿的接口拆分成更小的和更具体的接口。接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署。

1.6 更好的可扩展性——迪米特原则

通俗的讲,一个类应该对自己需要耦合或调用的类知道的最少,类的内部如何实现与调用者没关系,调用者只需要知道它需要的方法即可。

个人理解:“这个说的就是封装,尽量将相同的功能封装在一个类,只对外暴露需要的接口,其他的都不可见”。

终于整理完了第一章,这章说的核心思想就是面向对象的三大特点,继承、封装、多态。面向接口编程。