其实《设计模式之禅》关于原则的部分,我最喜欢的就是《迪米特法则》没什么特殊原因,前段时间部门培训,让我出个培训内容,就是选择的迪米特法则。其实原因很简单,就因为看它名字都不知道究竟是拿来干啥的。
先臭美一下《设计模式》——目录,然后让我们进入正题。
所以究竟什么是迪米特法则呢?
迪米特法则(Law of Demeter,LoD)也称为最少知识原则(Least Knowledge Principle,LKP),虽然名字不同,但描述的是同一个规则:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的这么多public方法,我就调用这么多,其他的我一概不关心。
对于这个定义说实话我还是挺喜欢的,但也是生活中我很难做到的一点,那就是“我就调用这么多,其他的我一概不关心”。其实在小的公司,这样的性格是不好的,毕竟很难做到一个萝卜一个坑,大家或多或少都需要承担一些职能外的任务。而且从个人成长的角度来说,其实也是参与越多,收获也会越多。
有感一段废话,可以跳过不看。职场中有一种情况,那就是A只做十件事,而且十件事都完成的很好;而B需要做一百件事,但是有一件事没有做好。这两个人谁的年终评估会更高一些呢?当然,这里不是劝大家“各人自扫门前雪,莫管他人瓦上霜”(虽然,这是迪米特法则提倡的)。只是有这么几个建议:
当然,上面这么多废话,主要目的就是想说明,当我们的代码不遵守迪米特法则的时候,一旦出现问题,要面临这么多的麻烦事。毕竟你作为主角还能为自己申辩一下,而我们可怜的代码,只能被别人吐槽“这代码真烂”,却无力反抗啊。
而《设计模式之禅》中,迪米特法则对类的低耦合做出了如下四层定义:
迪米特法则还有一个英文解释是:Only talk to your immediate friends(只与直接的朋友通信。)什么叫做直接的朋友呢?每个对象都必然会与其他对象有耦合关系,两个对象之间的耦合就成为朋友关系。
对于一个吃货来说,这个时候必须要吃可乐鸡翅了!
为了吃可乐鸡翅的,我们总共有两种实现途径:
先说说自己做,最基本的,我们需要知道配料,还需要知道制作可乐鸡翅的菜谱。当然,这些都不重要。作为一个懒汉来说,我最怕的是还要去购买食材,买回来以后还要自己制作!
以上可乐鸡翅制作步骤来自下厨房
public class Blog {
@Test
public void order() {
MySelf mySelf = new MySelf();
mySelf.order(new Waitress());
}
}
class ColaChickenWings {
private ChickenWings chickenWings;
private Cola cola;
public boolean cooked() {
return 0 == new Random().nextInt(5);
}
public ChickenWings getChickenWings() {
return chickenWings;
}
public void setChickenWings(ChickenWings chickenWings) {
this.chickenWings = chickenWings;
}
public Cola getCola() {
return cola;
}
public void setCola(Cola cola) {
this.cola = cola;
}
/**
* 鸡翅
*/
class ChickenWings {
}
/**
* 可乐
*/
class Cola {
}
}
class MySelf {
public void order(Waitress waitress) {
ColaChickenWings colaChickenWings = new ColaChickenWings();
// 买鸡翅
if (null == colaChickenWings.getChickenWings()) {
colaChickenWings.setChickenWings(colaChickenWings.new ChickenWings());
}
// 买可乐
if (null == colaChickenWings.getCola()) {
colaChickenWings.setCola(colaChickenWings.new Cola());
}
waitress.orderColaChickenWings(colaChickenWings);
}
}
class Waitress {
public void orderColaChickenWings(ColaChickenWings colaChickenWings) {
if (null != colaChickenWings) {
while (true) {
if (colaChickenWings.cooked()) {
System.out.println("美味的可乐鸡翅出锅喽!!!");
break;
} else {
System.out.println("马上就好了");
}
}
}
}
}
说到去饭店,这个步骤就复杂了,我们到饭店以后还要高喊一声:
之后只需要等上那么一会,可爱的服务员小姐姐就把可乐鸡翅端到我面前来了!!!惊不惊喜?意不意外?
显而易见,去饭店这种选择就符合了我们的最小知识原则,而我们只需要与可爱的服务员小姐姐成为朋友,她就会点好菜,再给你端上来就好了,而不需要我们去了解可乐鸡翅那么繁琐的制作过程。
public class Blog {
@Test
public void order() {
MySelf mySelf = new MySelf();
mySelf.order(new Waitress());
}
}
class ColaChickenWings {
private ChickenWings chickenWings;
private Cola cola;
public boolean cooked() {
// 买鸡翅
if (null == chickenWings) {
chickenWings = new ChickenWings();
}
// 买可乐
if (null == cola) {
cola = new Cola();
}
return 0 == new Random().nextInt(5);
}
/**
* 鸡翅
*/
class ChickenWings {
}
/**
* 可乐
*/
class Cola {
}
}
class MySelf {
public void order(Waitress waitress) {
waitress.orderColaChickenWings();
}
}
class Waitress {
public void orderColaChickenWings() {
ColaChickenWings colaChickenWings = new ColaChickenWings();
while (true) {
if (colaChickenWings.cooked()) {
System.out.println("美味的可乐鸡翅出锅喽!!!");
break;
} else {
System.out.println("马上就好了");
}
}
}
}
当然,这里还是需要注意一点,那就是虽然老话说“朋友的朋友是朋友”,但是你找朋友的朋友借个钱试试?
一个类只和朋友交流,不与陌生类交流,不要出现getA().getB().getC().getD()这种情况(在一种极端的情况下允许出现这种访问,即每一个点号后面的返回类型都相同),类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量不引入一个类中不存在的对象,当然,JDK API提供的类除外。
人和人之间是有距离的,太远关系逐渐疏远,最终形同陌路;太近就相互刺伤。对朋友关系描述最贴切的故事就是:两只刺猬取暖,太远取不到暖,太近刺伤了对方,必须保持一个既能取暖又不刺伤对方的距离。迪米特法则就是对这个距离进行描述,即使是朋友类之间也不能无话不说,无所不知。
还是说我们的可乐鸡翅,当美丽的小姐姐想要吃可乐鸡翅的时候怎么办呢?还是两个情况:
由于我对小姐姐比较熟悉,知道一些小姐姐的要求,在做可乐鸡翅的时候自然需要考虑进去
public class Blog {
@Test
public void order() {
MySelf mySelf = new MySelf();
mySelf.cookColaChickenWings(new CuteGirl());
}
}
class ColaChickenWings {
private Calorie calorie = new Calorie();
private Garlic garlic = new Garlic();
public Calorie getCalorie() {
return calorie;
}
public void setCalorie(Calorie calorie) {
this.calorie = calorie;
}
public Garlic getGarlic() {
return garlic;
}
public void setGarlic(Garlic garlic) {
this.garlic = garlic;
}
class Calorie {
}
class Garlic {
}
}
class MySelf {
public void cookColaChickenWings(CuteGirl cuteGirl) {
ColaChickenWings colaChickenWings = new ColaChickenWings();
if (null != colaChickenWings.getCalorie()) {
colaChickenWings.setCalorie(null);
}
if (null != colaChickenWings.getGarlic()) {
colaChickenWings.setGarlic(null);
}
cuteGirl.eat(colaChickenWings);
}
}
class CuteGirl {
public void eat(ColaChickenWings colaChickenWings) {
if (null != colaChickenWings.getGarlic() || null != colaChickenWings.getCalorie()) {
System.out.println("不好吃,再也不理你了");
}
}
}
public class Blog {
@Test
public void order() {
MySelf mySelf = new MySelf();
mySelf.cookColaChickenWings(new CuteGirl());
}
}
class ColaChickenWings {
private Calorie calorie = new Calorie();
private Garlic garlic = new Garlic();
public Calorie getCalorie() {
return calorie;
}
public void setCalorie(Calorie calorie) {
this.calorie = calorie;
}
public Garlic getGarlic() {
return garlic;
}
public void setGarlic(Garlic garlic) {
this.garlic = garlic;
}
class Calorie {
}
class Garlic {
}
}
class MySelf {
public void cookColaChickenWings(CuteGirl cuteGirl) {
cuteGirl.eat(new ColaChickenWings());
}
}
class CuteGirl {
public void eat(ColaChickenWings colaChickenWings) {
if (null != colaChickenWings.getGarlic()) {
colaChickenWings.setGarlic(null);
}
if (null != colaChickenWings.getCalorie()) {
colaChickenWings.setCalorie(null);
}
System.out.println("味道还行");
}
}
看看,是不是省心了很多(是不是注孤生不在考虑范围内)
一个类公开的public属性或非法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。因此,为了保持朋友类间的距离,在设计时需要反复衡量:是否还可以再减少public方法和属性,是否可以修改为private、package-private(包类型,在类、方法、变量前不加访问权限,则默认为包类型)、protected等访问权限,是否可以加上final关键字等。
迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。
这一条又说明了什么?可乐鸡翅还是要会做的!毕竟就算饭店倒闭了,就算小姐姐离开了我,至少我还是有可乐鸡翅吃的!
在实际应用中经常会出现这样一个方法:放在本类中也可以,放在其他类中也没有错,那怎么去衡量呢?你可以坚持这样一个原则:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。
而从前面的例子也可以看得出来,实际就是尽可能将类自身能够完成的判断、流程等,在该类内部完成,只对外提供一个调取的public即可,而不是将这些都暴露在外,让调用的类自己去判断(比如点菜,和陌生小姐姐吃可乐鸡翅)。
好吧,又冒出来了一个新名词Serializable,它究竟是什么意思呢?
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
好了,下面来看看在《设计模式之禅》中关于这部分的介绍:
在一个项目中使用RMI(Remote Method Invocation,远程方法调用)方式传递一个VO(Value Object,值对象),这个对象就必须实现Serializable接口(仅仅是一个标志性接口,不需要实现具体的方法),也就是把需要网络传输的对象进行序列化,否则就会出现NotSerializableException异常。突然有一天,客户端的VO修改了一个属性的访问权限,从private变更为public,访问权限扩大了,如果服务器上没有做出相应的变更,就会报序列化失败,就这么简单。
对于这段话,我个人的理解是:“ 能 序 列 化 就 序 列 化 , 不 序 列 化 可 能 会 挂 ! \color{red}{能序列化就序列化,不序列化可能会挂!} 能序列化就序列化,不序列化可能会挂!”