跟我学:设计模式之谈谈对面向对象的六大原则的理解

导读

在软件设计的过程中设计模式占着举足轻重的作用,高可用的程序必定是建立在良好的设计上的,而设计模式正式这些良好设计的积累总结。设计模式又是符合面向的对象的六大原则而拓展出来的,如同抽象的具体实现,给软件开发者以指导作用。因此面向对象的设计原则是重中之重,下面将一一分析这六大原则。

谈谈对面向对象编程的原则的理解

当面试官问这道题是,他想考察什么内容呢?

  • 是否了解面向对象的原则及作用
  • 是否了解面向对象语言(Java)的特性
  • 是否有对原则有过深入的实战经验

面向对象编程的原则

面向对象编程的原则分别有六个

  • 单一职责原则:就一个类而言,应该仅有一个能引起他变化的原因
  • 开放与关闭原则:面对修改关闭,面对拓展开发
  • 里氏替换原则:在引用基类的地方,可以用子类去替换,且不会影响程序的稳定性
  • 依赖倒置原则:高层不依赖底层,都应该依赖抽象。底层不依赖细节,应该依赖抽象
  • 接口隔离原则:暴露给客户端的接口,应该建立在最小的原则,不需暴露客户不需要的接口
  • 迪米特原则:最少知道原则,一个类应该尽量减少持有过多的引用

Java的特性

  • 封装:确保数据的安全
  • 继承:保证了程序的拓展能力
  • 多态:保证了程序的灵活性

深入理解

踏出优化的第一步—>单一职责原则

单一职责原则英文为Single Resposibility Principle,简单来说,一个类中应该是一组相关性很高的函数、数据的封装。但关于单一职责的划分界限并不是那么清晰,很多时候需要靠个人经验的界定。在开发功能之初,往往会将功能实现放在第一位。变量,方法都会堆积在一个类中方便调用,如一个简单的图片加载器包括:缓存的功能,网络图片的加载,未优化前代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class ImageLoader {
LruCache<String, Bitmap> mImageCache;
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
Handler mHandler = new Handler(Looper.getMainLooper());

public ImageLoader() {
initImageCache();
}

private void initImageCache() {
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 dispalyImage(String imageUrl, ImageView imageView) {
Bitmap bitmap = mImageCache.get(imageUrl);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(imageUrl);

mExecutorService.submit(new Runnable() {

@Override
public void run() {
Bitmap bitmap1 = downloadImage(imageUrl);
if (bitmap1 == null) {
return;
}
if (imageView.getTag().equals(imageUrl)) {
updateImageView(bitmap1, imageView);
}
mImageCache.put(imageUrl, bitmap1);
}
});
}


public void updateImageView(Bitmap bitmap, ImageView imageView) {
mHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}

public Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url1 = new URL(imageUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url1.openConnection();
bitmap = BitmapFactory.decodeStream(urlConnection.getInputStream());
urlConnection.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;

}
}

但如果了解单一职责的原则,我们就应该将他分为两个类进行实现ImageCache负责缓存,ImageLoader负责加载图片。优化后代码如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class ImageLoaderV1 {
ImageCacheV1 mImageCache; // 将缓存的初始化及添加获取方法移到同一个类
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

Handler mHandler = new Handler(Looper.getMainLooper());

public ImageLoaderV1() {
mImageCache = new ImageCacheV1();
}

public void dispalyImage(String imageUrl, ImageView imageView) {
// ....
mExecutorService.submit(new Runnable() {

@Override
public void run() {
Bitmap bitmap1 = downloadImage(imageUrl);
if (bitmap1 == null) {
return;
}
if (imageView.getTag().equals(imageUrl)) {
updateImageView(bitmap1, imageView);
}
mImageCache.put(imageUrl, bitmap1);
}
});
}
// ....
}

// 缓存类
public class ImageCacheV1 {
LruCache<String, Bitmap> mImageCache;

public ImageCacheV1() {
initImageCache();
}

private void initImageCache() {
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 put(String url, Bitmap bitmap) {
mImageCache.put(url, bitmap);
}

public void remove(String url) {
mImageCache.remove(url);
}

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

单一职责原则充分体现了面向对象特性中的封闭

让程序更稳定,更灵活—>开闭原则

开闭原则英文为Open Close Principle,是Java最基础的设计原则,它指导我们设计更稳定和灵活的系统。它的定义为对修改封闭,对扩展开放,要确保原有软件模块的正确性以及尽量少的影响原有模块就应该遵循开闭原则。通过分析上面的图片加载器,我们发现上面的图片加载器只是内存的缓存,我们需要扩展出磁盘缓存可用用户选择,这时我么会发现添加新的缓存方式需要修改原有的代码,且如果还需要扩展双缓存机制(内存+磁盘),则又需要修改原来的代码,这时候我们发现ImageCache这个类不支持缓存的拓展。这时候我们就应该考虑将缓存类进行抽象,定义出不同的缓存实现供ImageLoader引用,动态的添加不同的缓存方式,使程序更加稳定和灵活。代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//  缓存接口
public interface IImageCache {

void put(String url, Bitmap bitmap);

Bitmap get(String url);

}
// 内存缓存 同ImageCacheV1,只添加接口实现
public class ImageCacheV2 implements IImageCache{
// ....
}
// 磁盘缓存
public class ImageDiskCacheV2 implements IImageCache{
String cacheDie = "/Sdcard/cache/";

public void put(String url, Bitmap bitmap) {
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(cacheDie + url);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}

}
}
}

public Bitmap get(String url) {
return BitmapFactory.decodeFile(cacheDie + url);
}
}

// ImageLoader
public class ImageLoaderV2 {
ImageCacheV2 mImageCache;
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

Handler mHandler = new Handler(Looper.getMainLooper());

public ImageLoaderV2() {
}

// 通过用户传入指定的缓存实现,注入缓存实现
public void setmImageCache(ImageCacheV2 mImageCache) {
this.mImageCache = mImageCache;
}
// ......
}

由此,我们可以愉快的进行缓存的拓展,同时不需要修改原来的代码,符合开闭原则

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

里氏替换原则英文是Liskov Substitution Principle, 定义为所有引用基类的地方必须能透明的使用其子类的对象,里氏替换原则就是依赖于java特性中的继承和多态两大特性,而核心就是抽象,能使子类替换父类就是继承。

继承的优点

  • 代码重用,减少创建类的成本,每个子类都拥有父类的属性和方法
  • 子类和父类基本相似
  • 提高代码的可扩展性

缺点

  • 继承是侵入性的,只有继承就必须拥有父类的属性和方法
  • 可能造成子类代码冗余、灵活性降低,因为子类必须拥有父类的属性和方法

上面内存缓存和磁盘缓存都可以替代ImageLoader的变量,IImageCache就是体现了里氏替换原则原则。里氏替换原则和开闭原则往往是生死相依,不弃不离,里氏替换达到了对扩展开放对修改封闭的效果,两者都强调了一个重点特性抽象

让项目拥有变化的能力—>依赖倒置