00.6 ANDROID 面向对象的六大原则——迪米特原则

返回设计模式博客目录
|
第一篇:单一职责原则
第二篇:开闭原则
第三篇:里氏替换原则
第四篇:依赖倒置原则
第五篇:接口隔离原则
第六篇:迪米特原则


迪米特原则


英文全称为 Law of Demeter,LOD,也称为最少知识原则,意思都是一个对象应该对其他对象有最少的了解。通俗的讲,一个类应该对自己需要耦合或调用的类知道得最少,类的内部如何实现与调用者或者依赖者没关系,调用者或者依赖者只需要知道它需要的方法即可,其他的可一概不用管。类与类之间的关系越密切,耦合越大,当一个类发生变化时,对另一个类的影响也越大。

迪米特原则还有一个英文解释是 Only talk to your immediate friends,翻译过来就是:只与直接的朋友通信。什么叫做直接的朋友?每个对象都必然会与其他对象有耦合关系,两个对象之间的耦合就成为了朋友关系,这种关系的类型有很多,如组合、聚合、依赖等。

下面我们就以租房为例来讲讲迪米特原则的应用。


举例:在北京租房


在北京租房绝大多数都是通过中介找房。我们设定的情况为:我只要求房间的面积和租金,其他的一概不管,中介将符合我们要求的房子提供给我就可以。下面我们看看这个示例。

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
/**
* 房间
*/
public class Room {
public float area;
public float price;
public Room(float area, float price) {
this.area = area;
this.price = price;
}
@Override
public String toString() {
return "Room{" + "area=" + area + ", price=" + price + '}';
}
}
/**
* 中介
*/
public class Mediator {
List<Room> mRooms = new ArrayList<>();
public Mediator() {
for (int i = 0; i < 5; i++) {
mRooms.add(new Room(14 + i, (14 + i) * 150));
}
}
public List<Room> getAllRooms() {
return mRooms;
}
}
/**
* 租户
*/
public class Tenant {
public void rentRoom(float roomArea, float roomPrice, Mediator mediator) {
List<Room> rooms = mediator.getAllRooms();
for (Room room : rooms) {
if (isSuitable(roomArea, roomPrice, room)) {
System.out.println("租到房间啦!" + room);
break;
}
}
}
private boolean isSuitable(float roomArea, float roomPrice, Room room) {
return room.area >= roomArea && room.price <= roomPrice;
}
}

从上面的代码可以看到,Tenant 不仅依赖了 Mediator 类,还需要频繁地与 Room 类打交道。租户类的要求只是通过中介找到一间适合自己的房间罢了,如果把这些检测条件都放在 Tenant 类中,那么中介类的功能就被弱化,而且导致了 Tenant 与 Room 的耦合较高,因为 Tenant 必须知道许多关于 Room 的细节。当 Room 变化时 Tenant 也必须跟着变化。Tenant 又与 Mediator 耦合,这就出现了纠缠不清的关系。这个时候就需要我们分清谁才是我们真正的“朋友”,在我们所设定的情况下,显然是 Mediator。上述代码的结构下图所示。

既然是耦合太严重,那我们就只能解耦了。首先要明确的是,我们只和我们的朋友通信,这里就是指 Mediator 对象。必须将 Room 相关的操作从 Tenant 中移除,而这些操作案例应该属于 Mediator。我们进行如下重构。

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
/**
* 中介
*/
public class Mediator {
List<Room> mRooms = new ArrayList<>();
public Mediator() {
for (int i = 0; i < 5; i++) {
mRooms.add(new Room(14 + i, (14 + i) * 150));
}
}
public Room rentOut(float area, float price) {
for (Room room : mRooms) {
if (isSuitable(area, price, room)) {
return room;
}
}
return null;
}
private boolean isSuitable(float roomArea, float roomPrice, Room room) {
return room.area >= roomArea && room.price <= roomPrice;
}
}
/**
* 租户
*/
public class Tenant {
public void rentRoom(float roomArea, float roomPrice, Mediator mediator) {
mediator.rentOut(roomArea, roomPrice);
}
}

重构后的结构图如下所示。

只是将对于 Room 的判定操作移到了 Mediator 类中,这本应该是 Mediator 的职责,根据租户设定的条件查找符合要求的房子,并且将结果交给租户就可以了。租户并不需要知道太多关于 Room 的细节,比如与房东签合同,房东的房产证是不是真的,房内的设施坏了之后要找谁谁修等。当我们通过我们的“朋友”——中介租了房之后,所有的事情我们都通过与中介沟通就好了,房东、维修师傅等这些角色并不是我们直接的“朋友”。“只与直接的朋友通信”这简单的几个字就能够将我们从复杂的关系网中抽离出来,使程序耦合度更低、稳定性更好。


举例二:ImageCache


前面博客中的图片加载器项目,ImageCache 就是用户的直接朋友,而 SD 卡缓存内部使用了 FileOutputStream,这个 FileOutputStream 就不属于用户的直接朋友了。因此,用户完全不知道它的存在,用户只需要与 ImageCache 对象打交的即可。将图片存到 SD 卡中的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// SD 卡缓存 DiskCache 类
public class DiskCache implements ImageCache {
private static String CACHE_DIR =
Environment.getExternalStorageDirectory() + "/";
public Bitmap get(String url) {
return BitmapFactory.decodeFile(CACHE_DIR
+ ImageUtil.urlToMd5(url));
}
public void put(String url, Bitmap bitmap) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(CACHE_DIR
+ ImageUtil.urlToMd5(url));
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
CloseUtils.closeQuietly(fileOutputStream);
}
}
}

现在领导要求使用 jake wharton 的 DiskLruCache 来替换 FileOutputStream。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void put(String url, Bitmap bitmap) {
DiskLruCache.Editor editor = null;
try {
editor = mDiskLruCache.edit(url);
if (null != editor) {
OutputStream outputStream = editor.newOutputStream(0);
if (writeBitmapToDisk(bitmap, outputStream)) {
// 写入 Disk 缓存
editor.commit();
} else {
editor.abort();
}
CloseUtils.closeQuietly(outputStream);
}
} catch (Exception e) {
e.printStackTrace();
}
}

SD 卡缓存的具体实现虽然被替换了,但用户根本不会感知到。因为用户根本不知道 FileOutputStream 和 DiskLruCache 的存在,他们没有与 FileOutputStream 或 DiskLruCache 进行通信,他们只认识直接“朋友”——ImageCache,ImageCache 将一切细节隐藏在直接“朋友”的外衣之下,使得系统具有更低的耦合性和更好的可扩展性。