面向对象编程的六大原则
- 单一职责原则
- 开闭原则
- 里氏替换原则
- 依赖倒置原则
- 接口隔离原则
- 迪米特原则
更好的可拓展性--迪米特原则
迪米特原则的英文全称是Law of Demeter,缩写是LOD,也称为最少知识原则。
定义是:一个对象应该对其他对象有最少的了解。
直白的说,就是让对象只与最直接的朋友进行联系。那么什么是最直接朋友呢?每个对象都必然会与其他类有耦合关系,两个对象之间互相耦合就为朋友关系,这种关系类型有很多,比如组合,聚合,依赖。
下面,我们就通过举例来说明迪米特原则。
相信在外务工的朋友,都有租房的经历,一般情况下大家都会通过中介找房。我们通过租房进行举例,我们设定条件为:我只要求房子的租金和面积,中介负责将合适的房子介绍给我。以下是代码实例。
// 房子
public class House {
public float area;
public float price;
public House(float area, float price) {
this.area = area;
this.price = price;
}
@Override
public String toString() {
return "House{" +
"area=" + area +
", price=" + price +
'}';
}
}
// 中介
public class Agency {
List mHouses = new ArrayList<>();
public Agency() {
for (int i = 0; i < 5; i++) {
mHouses.add(new House(100 + i, (100 + i) * 200));
}
}
public List getHouses() {
return mHouses;
}
}
//租户
public class Tenant {
public float houseArea;
public float housePrice;
public static final float diffPrice = 200.001f;
public static final float diffArea = 0.00001f;
public void rentHouse(Agency agency) {
List mHouses = agency.getHouses();
for (House house : mHouses) {
if (isRightHouse(house)) {
System.out.println("找到合适的房子了!\n" + house.toString());
break;
}
}
}
private boolean isRightHouse(House house) {
return Math.abs(house.price - housePrice) < diffPrice
&& Math.abs(house.area - houseArea) < diffArea;
}
}
从上面的代码我们可以看到,我们的租户类(Tenant)不仅依赖了中介类(Agency),还要频繁地与我们的房子类(House)打交道。我们的需求是:我只要求房子的租金和面积,中介负责将合适的房子介绍给我。但是现在是我们拿到了一大堆房子数据然后自己进行分析和计算,这样一来,中介的功能就被弱化了,并且导致我们的租户类(Tenant)和房子类(House)的耦合过高,因为作为租户而言,我们必须知道房子(House)的细节。而此时租户类(Tenant)又和中介类(Agency)保持联系。这个时候我们就有必要进行一定的代码调整,去区分清楚,究竟谁是谁的直接朋友,让我们的代码清晰起来。
我们通过需求进行分析,可以得出以下几点:
- 租户只负责租房,提出房子的大小要求和租金要求,其他的要交给中介来做。
- 租户不参与房子的分析比对。
- 中介负责与客户进行交流,并且为客户进行房子的比对和筛选。
如此一来,我们可以根据需求对代码进行如下调整:
public class Agency {
List mHouses = new ArrayList<>();
public Agency() {
for (int i = 0; i < 5; i++) {
mHouses.add(new House(100 + i, (100 + i) * 200));
}
}
public House rentHouse(float area, float price) {
for (House house : mHouses) {
if (isRightHouse(area, price, house)) {
System.out.println("找到合适的房子了!\n" + house.toString());
return house;
}
}
return null;
}
private boolean isRightHouse(float area, float price, House house) {
return Math.abs(house.price - price) < Tenant.diffPrice
&& Math.abs(house.area - area) < Tenant.diffArea;
}
}
public class Tenant {
public float houseArea;
public float housePrice;
public static final float diffPrice = 200.001f;
public static final float diffArea = 0.00001f;
public void rentHouse(Agency agency) {
System.out.println("找到合适的房子了:" + agency.rentHouse(houseArea, housePrice));
}
}
此处我们经过一些简单的调整,将对房子是否符合要求的判断放到了中介里,这其实本就应该是中介的职责,根据用户设定的条件去寻找房子,找到了就告诉用户就可以了。而用户也不需要知道有多少房源,每个房子是什么样,他只需要询问中介,中介告知结果即可。
从上述代码我们可以看到,我们通过一次简单的代码修改后,代码的耦合性已经降低了很多,同时,他们都达成了与最直接的朋友联系的条件,将各个角色从复杂的关系中抽离出来,使程序的耦合更低,稳定性更好。
这里我们再次回忆一下我们ImageLoader中的代码,就拿SD卡缓存来说,ImageCache就是用户的直接朋友,而SD卡缓存内部却是使用了DiskCache实现,这个DiskCahce就不是用户的直接朋友了,因此,其实用户完全不需要知道有这样的缓存存在,用户只需要和ImageCache打交道就好了,我们之前的代码我再次优化过,使用了JakeWharton的DiskLruCache,最后代码如下:
public void put(String url, Bitmap bitmap) {
DiskLruCache.Editor editor = null;
//如果没有在内存缓存和磁盘缓存中找到对对应的缓存,从网络上请求数据
//写入缓存
editor = mDiskLruCache.edit(url);
try {
if (editor != null) {
OutputStream out = editor.newOutputStream(0);
if (writeBitmapToDisk(value, out)) {
//写入SD卡
editor.commit();
} else {
editor.abort();
}
}
} catch (IOException e) {
e.printStackTrace();
}
CloseUtils.colse(out);
}
用户在使用SD卡缓存的时候,根本不知道DiskLruCache的实现,这就很好地对用户隐藏了具体的代码实现。当你能够自己去写DiskLruCache的时候,你就可以随意替换掉此处我们引用的JakeWharton大神的DiskLruCache了。
总结#
一不小心,六个章节的《走向面向对象的六大原则》就到此结束了,在这里感谢何红辉、关爱民先生所著的《Android设计模式》一书,给我了很多启发和灵感,并且示例上提供了很多借鉴。
时间总是很快,快到你都注意不到自己的胡渣子又冒出来了,在我们开发的过程中,最难的不是开发出用户所需要的功能,而是在后续的升级和维护过程中,让系统能够拥抱变化,不因为版本的迭代更新而破坏模块的完整性和代码的条理性,保证代码的低耦合,高可拓展性,高内聚,在经理了N个版本的迭代以后,软件系统依旧能够保持稳健,灵活,清晰的架构。
当然啦,这只是一个理想的情况,代码迭代而引发的坑,我们爬的还少吗?为了少爬坑,为了让自己的代码更漂亮,系统更稳定,我们必须朝着这个方向去努力,遵循面向对象的六大原则,就是我们走出的,第一步。
明天的将会写关于单例模式的一些理解和示例,感谢大家的一直支持,我们共同进步!
谢谢。