一、单一职责原则
定义:就一个类而言,应该仅有一个引起他变化的原因
问题:对职责的定义,什么是类的职责,如何划分类的职责
需求:实现图片加载,将图片缓存起来
package com.example.homework;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.util.LruCache;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 图片加载类
* 实现图片加载并将图片缓存起来
* */
public class ImageLoaderold {
//图片缓存
LruCache<String, Bitmap> mImageCache;
//线程池,线程数量为CPU的数量
ExecutorService mExecutorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//UI Handler
Handler mUiHandler=new Handler(Looper.getMainLooper());
public ImageLoaderold(){
initImageCache();
}
private void initImageCache() {
//计算可使用的最大内存
final int maxMemory=(int) (Runtime.getRuntime().maxMemory()/1024);
//取四分之一的可用内存作为缓存
final int cacheSize=maxMemory/4;
mImageCache=new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes()*bitmap.getHeight()/1024;
}
};
}
public void displayImage(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);
}
private void updateImageView(ImageView imageView, final Bitmap bmp) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bmp);
}
});
}
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap=null;
URL url= null;
try {
url = new URL(imageUrl);
final HttpURLConnection conn=(HttpURLConnection) url.openConnection();
bitmap= BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
});
}
}
问题:所有功能都写在一个类里
解决:拆分,把各个功能独立出来,让他们满足单一职责原则
修改:
将ImageLoader一分为二,ImageLoader只负责图片加载的逻辑,添加一个ImageCache类用于处理图片缓存,并且 ImageCache只负责处理图片缓存的逻辑
1、当缓存相关的逻辑需要修改时,不需要修改ImageLoader类
2、图片加载的逻辑需要修改时,也不会影响到缓存处理逻辑
package com.example.homework;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.util.LruCache;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 图片加载类
* 实现图片加载并将图片缓存起来
* */
public class ImageLoader {
//图片缓存
LruCache<String, Bitmap> mImageCache;
//线程池,线程数量为CPU的数量
ExecutorService mExecutorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//UI Handler
Handler handler= new Handler(Looper.getMainLooper());
private void updateImageView(final ImageView imageView,final Bitmap bmp){
handler.post(new Runnable(){
@Override
public void run() {
imageView.setImageBitmap(bmp);
}
});
}
//加载图片
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)){
updateImageView(imageView,bitmap);
}
mImageCache.put(url,bitmap);
}
});
}
public Bitmap downloadImage(String imageUrl){
Bitmap bitmap=null;
URL url= null;
try {
url = new URL(imageUrl);
final HttpURLConnection conn=(HttpURLConnection) url.openConnection();
bitmap= BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
ImageCache类:
package com.example.homework;
import android.graphics.Bitmap;
import android.util.LruCache;
/*
* 处理图片缓存
* */
public class ImageCache {
//图片LRU缓存
LruCache<String, Bitmap>mImageCache;
public ImageCache(){
initImageCache();
}
private void initImageCache() {
//计算可使用的最大内存
final int maxMemory=(int) (Runtime.getRuntime().maxMemory()/1024);
//取四分之一的可用内存作为缓存
final int cacheSize=maxMemory/4;
mImageCache=new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes()*bitmap.getHeight()/1024;
}
};
}
public void put(String url,Bitmap bitmap){
mImageCache.put(url,bitmap);
}
public Bitmap get(String url){
return mImageCache.get(url);
}
}
小结:
1、两个完全不一样的功能不应该放在一个类中
2、一个类应该是一组相关性很高的函数、数据的封装
二、开闭原则
定义:软件中的对象(类、模块、函数等)应该对于扩展是开放的,对于修改时封闭的
在现实开发中,只通过继承的方式来升级、维护原有系统不现实,往往修改原有代码和扩展代码同时存在
继承:已存在的 实现类对于修改是封闭的,但是新的实现类可以通过覆写父类的接口应对变化
案例:
问题:缓存系统中内存缓存解决了每次从网络加载图片的问题,但是内存有限,且容易失去,重新启动程序之后需要重新下载
解决:引入SD卡缓存
DiskCache 类 ,将图片缓存到SD卡中
package com.example.homework;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.util.LruCache;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 图片加载类
* 实现图片加载并将图片缓存起来
* */
public class ImageLoader {
//内存缓存
ImageCache mImageCache=new ImageCache();
//SD卡缓存
DiskCache mDiskCache=new DiskCache();
//是否使用SD卡缓存
boolean isUseDiskCache=false;
//线程池,线程数量为CPU的数量
ExecutorService mExecutorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//加载图片
public void displayImage(final String url,final ImageView imageView){
//判断使用哪种缓存
Bitmap bitmap=isUseDiskCache ? mDiskCache.get(url) : mImageCache.get(url);
if(bitmap!=null){
imageView.setImageBitmap(bitmap);
return;
}
//没有缓存,则提交线程池进行下载
}
public void useDiskCache(boolean useDiskCache){
isUseDiskCache=useDiskCache;
}
}
新增DiskCache添加了SD卡缓存的功能。
通过 useDiskCache方法来对使用哪种缓存进行设置 :
public void useDiskCache(boolean useDiskCache){
isUseDiskCache=useDiskCache;
}
ImageLoader imageLoader=new ImageLoader();
//使用SD卡缓存
imageLoader.useDiskCache(true);
//使用内存缓存
imageLoader.useDiskCache(false);
问题:使用内存缓存时用户就不能使用SD卡缓存,而用户需要两种策略的综合,首先优先使用内存缓存,如果内存缓存没有图片再使用SD卡 缓存,如果SD卡中也没有,最后才从网络上获取
解决:新建一个双缓存类
更新ImageLoader
问题:每次 加入新的缓存方法都需要修改 原来的代码,会使原来的代码逻辑越来越复杂,而且 无法自定义缓存,用户 不能自己实现缓存注入到ImageLoader中,可扩展性差
解决:尽量通过扩展的方式 来实现变化,而不是通过修改已有的代码来实现
UML图:
重构ImageLoader
将ImageCache提取成一个图片缓存的接口,用来抽象图片缓存的功能,接口的声明如下:
定义了获取和缓存图片两个函数
缓存的key是图片的URL,值是图片本身
内存缓存、SD卡缓存、双缓存都实现了该接口:
ImageLoader类中增加了一个函数setImageCache(ImageCache cache),用户可以通过该函数设置缓存实现。也就是依赖注入:
用户设置缓存实现:
总结:通过setImageCache(ImageCache cache)方法注入不同的缓存实现,使得ImageLoader更简单、健壮,使得其可扩展性、灵活性更高,MemoryCache、DiskCache、DoubleCache缓存图片的具体实现完全不一样,但是他们都实现了ImageCache接口,当用户需要 自定义实现缓存策略时,只需要新建一个实现ImageCache接口的类,然后构造该类的对象,并且通过setImageCache(ImageCache cache)方法注入到ImageLoader中,这样ImageLoader实现了可以变化的缓存 策略 ,而且扩展这些缓存策略不会 导致ImageLoader类的修改
三、里氏替换原则
定义1:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T2定义的所有程序p在所有的对象o1都代换成o2时,程序p的行为没有发生变化,那么类型T1是类型T2的子类型(继承关系)
定义2:所有引用基类的地方必须能透明地使用其子类的对象(通俗的说,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常
面向对象三大特征:继承、封装、多态
里氏替换原则依赖于继承和多态
了解android中window和view的关系:
代码实现:
窗口类:
建立视图抽象,测量视图的宽高为共用代码,绘制实现交给具体的子类:
文本控件类的具体实现:
ImageView的具体实现:
总结:
上例中,Window依赖于View,而View定义了一个视图抽象,measure是各个子类共享的方法,子类通过覆写View的draw方法实现具有各自特色的功能,任何继承自view类的子类都可以传递给show函数,就是所说的里氏替换,通过里氏替换可以自定义各式各样的View,然后传递给Window,Window负责组织View,并且将View显示到屏幕上
里氏替换原则的核心原理是抽象,抽象又依赖于继承,继承有如下的优缺点:
优点:
1、代码重用,减少创建类的成本,每个子类都拥有父类的方法和属性
2、子类与父类基本相似,但又与父类有所区别
3、提高代码的可扩展性
缺点:
1、继承是侵入性的,只要继承就必须拥有父类的所有属性和方法
2、可能造成子类代码冗余,灵活性降低,因为子类必须拥有父类的属性和方法
此图很好的反应了里氏替换原则,即MemoryCache、DiskCache、DoubleCache都可以替换ImageCache的工作,并且能够保证行为的正确性。ImageCache建立了获取缓存图片、保存缓存图片的接口规范,MemoryCache等根据接口规范实现了相应的功能,用户只需要在使用时指定具体的缓存对象 就可以动态的替换ImageLoader中的缓存策略,这就保证了可扩展性。
四、依赖倒置原则
依赖倒置原则有一下几个关键点:
(1)高层模块不应该依赖低层模块,两者都应该依赖其抽象
(2)抽象不应该依赖细节
(3)细节应该依赖抽象
抽象指的是接口或者抽象类,两者都是不能直接被实例化的。细节就是实现类,实现接口或者继承抽象类而产生的类就是细节,可以被直接实例化,也就是可以加上一个关键字new产生一个对象。高层模块就是调用端,低层模块就是具体实现类。
依赖倒置原则在java中的表现:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的,即面向接口编程或者面向抽象编程。
如ImageLoader直接依赖于MemoryCache,而MemoryCache是具体实现类,不是抽象类或者接口,这就导致ImageLoader直接依赖了具体细节,当MemoryCache不能满足ImageLoader而需要被其他缓存实现替换时,此时必须修改ImageLoader的代码
用户需要ImageLoader将图片同时缓存到内存和SD卡中,或者让用户自定义实现缓存,此时MemoryCache就无法满足需求。解决方法:将MemoryCache修改为DoubleCache,在DoubleCache中实现具体的缓存功能,修改ImageLoader如下:
还是依赖于实现类
解决方法:
依赖抽象,不依赖具体实现
针对图片缓存,建立ImageCache抽象,在抽象中增加get和put方法用以实现图片的存取每种缓存实现都必须实现这个接口,并且实现自己的存取方法。当用户需要使用不同的缓存实现时,直接通过依赖注入即可,也保证了系统的灵活性。
ImageCache缓存接口:
ImageLoader类:
总结:
在这里我们建立了ImageCache抽象,并且让ImageLoader依赖于抽象而不是具体细节。需求发生变化时,只需要实现ImageCache类完成相应的缓存功能,然后将具体的实现注入到ImageLoader即可实现缓存功能的替换,这就保证了缓存系统的高可扩展性,这就是依赖倒置原则。
五、接口隔离原则
定义1:客户端不应该依赖它不需要的接口
定义2:类间的依赖关系应该建立在最小的接口上
目的:系统解开耦合,从而容易重构、更改和重新部署
一个问题:在使用了OutputStream或者其他可以关闭的对象之后,我们必须保证他们最终被关闭了:
看上面这段代码,使用try…catch嵌套一些简单的代码,导致代码可读性差,并且容易出错。
解决:
java中Closeable接口,标识了一个可关闭的对象,FileOutputStream实现了这个接口
结论:closeQuitely方法可以运用到各类可关闭的对象中,保证了代码的重用性。CloseUtils的closeQuitely方法基本原理就是依赖于Closeable抽象而不是具体实现(依赖倒置原则),并且建立在最小化依赖原则的基础上,它只需要知道这个对象使可关闭的,其他的一律不关心,也就是我们说的接口隔离原则。
我们的案例中,ImageLoader中的ImageCache就是接口隔离原则的运用ImageLoader只需要知道该缓存对象有存、取缓存图片的接口即可,其他一概不管,这就使得缓存功能的具体实现对ImageLoader隐藏。
效果:
1、用最小化接口隔离实现了类的细节
2、使得系统具有更低的耦合性、更高的灵活性
前五大原则的总结:抽象、单一原则、最小化
六、迪米特原则
又称为最少知识原则:一个对象应该对其他对象有最少的了解,换句话说,只与直接的朋友通信。
什么叫做直接朋友?每个对象必然与 其他对象有耦合关系,两个对象之间的耦合就成为朋友关系,这种关系的类型有很多如 :组合、聚合、依赖
需求:我们租房子只要求房间的面积和租金,其他一律不管
中介:
租户:
小结:Tenant不仅仅依赖Mediator类,还频繁与Room类打交道,这样中介类的功能就被弱化了而且导致Tenant与Roon的耦合较高。
解决方法:解耦合
只和直接朋友通信
修改后:
将Room的判定操作移到Mediator类中,使得程序的耦合性降低,稳定性更好了。
SD卡缓存案例中:
ImageCache是用户的直接朋友,在SD卡缓存内部使用了jake wharton的DiskLruCache实现,这个DiskLruCache不属于用户的直接朋友,因为,用户不需要知道他的存在,只需要与ImageCache对象打交道即可。