标签:
在开发过程中,面向对象的六大原则非常重要,所以本节给大家带来了这六大原则的讲解,全文会以一个简单的ImageLoader为例进行讲解,为以后学习设计模式做铺垫。
定义:就一个类而言,应该仅有一个引起它变化的原因,简单来说,一个类中应该是一组相关性很高的函数、数据的封装。
一般情况下,我们会这样写一个ImageLoader:
/**
* 图片加载类
*/
public class ImageLoader {
//图片缓存
private LruCache<String,Bitmap> mImageCache;
//线程池,数量为CPU数量
private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public ImageLoader() {
initCache();
}
private void initCache() {
//可使用的最大内存
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 value) {
return value.getRowBytes()*value.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)){
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url,bitmap);
}
});
}
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap=null;
try {
URL url=new URL(imageUrl);
HttpURLConnection conn= (HttpURLConnection) url.openConnection();
bitmap= BitmapFactory.decodeStream(conn.getInputStream());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
但是这个类的耦合性太严重,所有的功能都写在一个类中了,如果功能增加了,ImageLoader类就会越来越大,代码越来越差,也就是可维护性很差,就会导致图片加载会越来越差。为了符合单一原则,我们应该做如下修改:
/**
* 图片加载类
*/
public class ImageLoader {
//图片缓存
private ImageCache mImageCache;
//线程池,数量为CPU数量
private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public ImageLoader() {
mImageCache=new ImageCache();
}
public void displayImage(final String url, final ImageView imageView) {
Bitmap bmp = mImageCache.get(url);
if (bmp != null) {
imageView.setImageBitmap(bmp);
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 imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
/**
* 图片缓存类
*/
public class ImageCache {
//图片缓存
private LruCache<String, Bitmap> mImageCache;
public ImageCache(){
initCache();
}
private void initCache() {
//可使用的最大内存
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 value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
public Bitmap get(String url) {
return mImageCache.get(url);
}
public void put(String url,Bitmap bmp) {
mImageCache.put(url,bmp);
}
}
此时ImageLoader只是负责加载图片的逻辑,ImageCache只是负责处理图片缓存的逻辑,这样职责也就非常的清晰了;当缓存相关的逻辑需要改变时,不需要修改ImageLoader类了,而图片加载的逻辑需要修改时也不会影响到缓存处理逻辑。
定义:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。
因此,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。
我们仍然以ImageLoader为例,假想一下,如果现在要求把这个ImageLoader再加一个SD卡缓存,你会怎么加呢?
我们一般会这样写:刚刚咱们学习了“单一职责原则”,所以我们知道肯定不能在ImageLoader里面加一个方法进行SD卡缓存,也不会在ImageCache中加SD卡缓存,因为每一个类都应该是单一的功能,仅有一个引起它变化的原因嘛。那自然会想到我再建一个DisCache类进行SD的缓存操作,在ImageLoader中加入一个DisCahce引用,手动调用里面的功能不就可以了吗?那问题来了,万一你把ImageLoader改错了呢?特别是在开发一款上市软件的时候,那将会出大问题呀?就也是违背了“开闭原则”。
那我们应该怎么做呢?思路应该是这样的:使用接口,因为接口是一种规范,进行缓存功能的实现无非就是put与get方法,存入与取出。所以先定义一个接口ImageCache,再定义一个MemoryCache与DiskCache分别实现ImageCache接口,在ImageLoader中利用多态性持有ImageCahce的引用,只在用户传入哪个ImageCache就会执行哪个缓存。代码如下:
/**
* 图片缓存接口
*/
public interface ImageCache {
public Bitmap get(String imageUrl);
public void put(String imageUrl,Bitmap bmp);
}
/**
* 内存缓存
*/
public class MemoryCache implements ImageCache{
//图片缓存
private LruCache<String, Bitmap> mImageCache;
public MemoryCache(){
initCache();
}
private void initCache() {
//可使用的最大内存
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 value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
public Bitmap get(String url) {
return mImageCache.get(url);
}
public void put(String url,Bitmap bmp) {
mImageCache.put(url,bmp);
}
}
/**
* SD卡缓存
*/
public class DiskCache implements ImageCache {
private String cacheDir = "sdcard/cache/";
@Override
public Bitmap get(String imageUrl) {
return BitmapFactory.decodeFile(imageUrl);
}
@Override
public void put(String imageUrl, Bitmap bmp) {
FileOutputStream fos=null;
try {
fos=new FileOutputStream(cacheDir+imageUrl);
bmp.compress(Bitmap.CompressFormat.JPEG,100,fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if (fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 图片加载类
*/
public class ImageLoader {
//图片缓存
private ImageCache mImageCache= new MemoryCache();
//线程池,数量为CPU数量
private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void setmImageCache(ImageCache cache){
mImageCache=cache;
}
public void displayImage(final String url, final ImageView imageView) {
Bitmap bmp = mImageCache.get(url);
if (bmp != null) {
imageView.setImageBitmap(bmp);
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 imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
通过setImageCache(ImageCache cache)方法注入不同的缓存实现,这样不仅能够使ImageLoader更简单、健壮,也使得ImageLoader的可扩展性、灵活性更高,用户可以随便设置自己的缓存。如果现在需要加入一个双缓存机制是不是也很容易实现了,在此就不再重复了,读者可以自己尝试尝试。
定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1的子类型。
定义2:所有引用基类的地方必须能透明地使用其子类的对象。
通俗的说:只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。
比如现在我要定义一个继承自ImageView的CircleImageView,可以将CircleImageView设置给ImageLoader.displayImage(),这就是所说的里氏替换原。通过里氏替换原,就可以自定义各式各样、千变万化的View。
里氏替换原原则的核心原理是抽象,抽象又依赖于继承这个特性,在OOP(面向对象编程)当中,继承的优缺点都相当明显。
优点如下:
代码重用,减少创建类的成本,每个子类都拥有父类的方法和属性;
子类与父类基本相似,但又与类有所区别;
提高代码的可扩展性。
缺点:
继承是侵入性的,只要继承就必须拥有父类的所有属性和方法;
可能造成子类代码冗余、灵活性降低,因为子类必须拥有父类拥有父类的属性和方法。
下面给出一个CircleImageView的实现地址:http://www.tuicool.com/articles/mQNFJ3,感兴趣的可以自己去实现一样。
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
高层模块就是调用端,低层模块就是具体实现类。依赖倒置原则在java语言中的表现就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生。
例如:上面的ImageLoader是一个很好有例子,它是依赖于ImageCache而不是某个具体的缓存机制,当用户需要实现其他缓存时,可以自己实现ImageCache接口来定义一个自己的缓存,也不需要改变ImageLoader中的任何代码。
依赖倒置原则有以下几个关键点:
高层模块不应该依赖细节
抽象不应该依赖细节
细节应该依赖抽象
定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
接口隔离原则将非常庞大、臃肿的接口拆分成更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。接口隔离原则的目的是系统解耦,从而容易重构、更改和重新部署。
例如:
public void put(String imageUrl, Bitmap bmp) {
FileOutputStream fos=null;
try {
fos=new FileOutputStream(cacheDir+imageUrl);
bmp.compress(Bitmap.CompressFormat.JPEG,100,fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if (fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在我们使用OutputStream或其他可关闭的对象时,我们必须保证它们最终被关闭,而各种try….catch嵌套都会使得代码的可读性很差,而且还容易发生错误,所以我们可以写一个统一的类来关闭这此对象。
public final class CloseUtils{
private CloseUtils(){}
/**
* 关闭Closeable对象
*/
public static void closeQuietly(Closeable closeable){
if (closeable != null){
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
现在可以将上面的put方法改写一下了
public void put(String imageUrl, Bitmap bmp) {
FileOutputStream fos=null;
try {
fos=new FileOutputStream(cacheDir+imageUrl);
bmp.compress(Bitmap.CompressFormat.JPEG,100,fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
CloseUtils.closeQuietly(fos);
}
}
代码简洁了很多,而且这个closeQuietly方法可以运用到各类可关闭的对象中,保证了代码的重用性。CloseUtils的closeQuietly方法的基本原理就是依赖于Closeable抽象而不是具体实现,并且建立在最小化依赖原则的基础之上,它只需要知道对象是可关闭的就可以了,这就是接口隔离原则。
再比如说,我们需要将缓存到sd卡中的图片进行其他处理,此时我们应该再写一个单独的接口来处理,而不是将这些处理方法写入到ImageCache中去,这样才会显得结构清晰,不冗余。
定义:一个对象应该对其他对象保持最少的了解。
通俗的说:一个类应该对自己需要耦合或调用的类知道得最少,类的内部如何实现与调用者或者依赖者没有关系,调用者或者依赖者只需要知道它需要的方法即可,其他的可一概不管。
例如:
public void put(String imageUrl, Bitmap bmp) {
FileOutputStream fos=null;
try {
fos=new FileOutputStream(cacheDir+imageUrl);
bmp.compress(Bitmap.CompressFormat.JPEG,100,fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
CloseUtils.closeQuietly(fos);
}
}
我们可以将上面的代码改为如下的代码:
public void put(String imageUrl, Bitmap bmp) {
DiskLruCache.Editor editor=null;
OutputStream os=null;
try {
editor=mDiskLruCache.edit(imageUrl);
if (editor != null){
os=editor.newOutputStream(0);
if (writeBitmapToDisk(bmp,os)){
editor.commit();
}else {
editor.abort();
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
CloseUtils.closeQuietly(os);
}
}
此时SD卡缓存的具体实现被替换了,但是对用户而言一点影响也没有,因为用户根本不知道DiskLruCache的存在,他们没有与DiskLruCache进行通信,他们只认识ImageCache,这使得系统具有更低的耦合性和更好的扩展性。
在应用开发过程中,这几个原则相当重要,你的程序里否健康,与这六大原则是息息相关的,我们不要刻板的去遵守这六大原则,而是要灵活的运用它们,这样才可以使你的程序的可维护性,可扩展性更好,更健康。
标签:
原文地址:http://blog.csdn.net/qq_31749345/article/details/51310437