明 嘉靖四十年 江南织造总局
小黄门唯唯诺诺的听完了镇守太监杨金水的训斥,赶忙回答:“知道了,干爹!”
“知道什么?!!”
杨金水打断了他的话,眼神突然变得凌厉起来:
“有些事情,不上秤没有四两重,上秤了,一千斤都挡不住。”
一言
迪米特法则,即最少知道原则。其它类的方法你想怎么实现都可以,不要在我引用的地方乱显摆。
概述
哲学是最接近世界本质的学科。理科生应该经常读到这样的论断:“生物的本质是化学,化学的本质是物理,物理的本质是数学,数学的本质是哲学。”
的确,从表现到反应、到作用、最后又到了关系。随着年龄的增长和阅历的积累,我们会越来越明晰的发现,无论是在软件设计还是日常生活,你能把事物关系处理的越好,整个体系就会越稳健。编程如此,人生亦然。
《哈利波特》中多方势力的角逐可以说精彩非凡。霍格沃兹、魔法部、食死徒、凤凰社…每个角色都给人留下了深刻的印象。错综复杂的关系使得正与邪之间的斗争变得充满变数。比如在小说中,魔法部名为司法部门,其中成员却鱼龙混杂,既有像亚瑟·韦斯莱一样心怀正义的凤凰社成员,也有背弃初心屈服于伏地魔的食死徒。
当面对复杂的对象关系,我们往往很难意识到无规则调用带来的风险。迪米特法则正是用于规范对象关系的一句箴言。它要求一个对象应该对其它对象保持最少的了解,也就是我们常说的低耦合,类与类之间的关系越密切,耦合度也就越大。
说到这里大家也许会想起我之前在说OCP原则时举得例子,工具方法无差别的被几百个应用模块引用是否合理?
我们不妨再进一步简化它的定义:每个对象应该只与他的直接朋友(建立关系)通信。
什么是直接朋友
每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。而耦合的方式很多:依赖,关联,组合,聚合等都是耦合关系。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。
也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
欢迎来到魔法世界
我觉得说了上面一大通,太抽象了。按我的风格,我们还是从一个有趣的情景出发来拆解下迪米特法则到底是个什么东西。
不好,他们抓住了韦斯莱
魔法部被邪恶势力食死徒完全的渗透了,现在他们想要得到凤凰社所有成员的名单和这些凤凰社成员的秘密。食死徒们想了一个堂而皇之的借口,成立魔法部成员管理委员会,核查所有魔法部成员的名单,同时核查凤凰社成员的名单。
对于凤凰社来说,被食死徒们知道了名单并不可怕,重要的是不能泄露凤凰社成员的秘密。
亚瑟·韦斯莱既是凤凰社成员又是魔法部成员,自然成为了食死徒的首要目标。
卑鄙的食死徒终于还是对亚瑟韦斯莱用了吐真剂。
public class MajicWorld {
public static void main(String[] args) {
MinistryOfMagicManager touch = new MinistryOfMagicManager();
touch.printAllGuys(new ArthurWeasley());
}
public static List<PhoenixSociety> initPs(){
List<PhoenixSociety> psList = new ArrayList<>();
PhoenixSociety p1 = new PhoenixSociety();
p1.setName("小天狼星 布莱克");
p1.setSecret("凤凰社的人我都认识");
psList.add(p1);
PhoenixSociety p2 = new PhoenixSociety();
p2.setName("莱姆斯 卢平");
p2.setSecret("我可以变成狼人");
psList.add(p2);
PhoenixSociety p3 = new PhoenixSociety();
p3.setName("西弗勒斯 斯内普");
p3.setSecret("我深爱着莉莉,她的儿子是我的软肋");
psList.add(p3);
PhoenixSociety p4 = new PhoenixSociety();
p4.setName("尼法朵拉 唐克斯");
p4.setSecret("死亡圣器的秘密可能是真的");
psList.add(p4);
return psList;
}
public static List<MinistryOfMagic> initMajicMembers(){
List<MinistryOfMagic> psList = new ArrayList<>();
MinistryOfMagic p1 = new MinistryOfMagic();
p1.setName("尤里克·甘普");
psList.add(p1);
MinistryOfMagic p2 = new MinistryOfMagic();
p2.setName("巴蒂·克劳奇");;
psList.add(p2);
MinistryOfMagic p3 = new MinistryOfMagic();
p3.setName("康奈利·福吉");
psList.add(p3);
MinistryOfMagic p4 = new MinistryOfMagic();
p4.setName("卢多·巴格曼");
psList.add(p4);
return psList;
}
}
//魔法部成员
class MinistryOfMagic{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//凤凰社成员
class PhoenixSociety{
private String name;
private String secret;//秘密
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
}
//亚瑟韦斯莱(罗恩的爸爸),他知道部分凤凰社成员的名单
class ArthurWeasley{
public List<PhoenixSociety> sayMembersOfPhoenixSociety(){
List<PhoenixSociety> psList = MajicWorld.initPs();
return psList;
}
}
//魔法部成员管理委员会
//这个部门应该只可以管理魔法部成员,以及亚瑟韦斯莱,因为亚瑟既是魔法部成员也是凤凰社成员
//但是凤凰社成员直接暴露在魔法部是很危险的
class MinistryOfMagicManager{
//返回魔法部的所有成员
public List<MinistryOfMagic> getList(){
return MajicWorld.initMajicMembers();
}
//输出魔法部成员和凤凰社成员
public void printAllGuys(ArthurWeasley arthur){
//给韦斯莱灌了吐真剂
List<PhoenixSociety> pss = arthur.sayMembersOfPhoenixSociety();
pss.forEach(e-> System.out.println("我是凤凰社成员:"+e.getName()+",我的秘密是:"+e.getSecret()));
System.out.println("-------------------------------------");
//获取全部魔法部成员
List<MinistryOfMagic> list = this.getList();
list.forEach(e-> System.out.println("魔法部成员:"+e.getName()));
}
}
下面我们来分析一下这段代码的问题,
魔法部成员管理委员会类(MinistryOfMagicManager)这个类的直接朋友应该是 魔法部成员类(MinistryOfMagic )和 亚瑟·韦斯莱类(ArthurWeasley),因为只有这两个类属于魔法部,而凤凰社成员并不都是魔法部的,所以凤凰社成员类(PhoenixSociety)并不应该出现在这个类中,这违反了迪米特法则。
因为对于亚瑟·韦斯莱(ArthurWeasley)这个类的设计缺陷,直接导致了食死徒可以通过魔法部成员管理委员会类(MinistryOfMagicManager)这个类获得凤凰社成员类(PhoenixSociety)的实现细节,进而盗取出凤凰社的秘密。
韦斯莱是个意志坚定的人,他不会出卖我们
我们已经看到了,问题出在了亚瑟·韦斯莱(ArthurWeasley)这个类的设计上。只要让他遵循迪米特法则,处理好实现细节就能规避掉耦合度提高的问题。
//亚瑟韦斯莱(罗恩的爸爸),他知道部分凤凰社成员的名单
class ArthurWeasley{
private List<PhoenixSociety> sayMembersOfPhoenixSociety(){
List<PhoenixSociety> psList = MajicWorldUnderDemeterPrinciple.initPs();
return psList;
}
public void printPs(){
List<PhoenixSociety> phoenixSocieties = this.sayMembersOfPhoenixSociety();
phoenixSocieties.forEach(e-> System.out.println("我是凤凰社成员:"+e.getName()+"。罪恶的食死徒,即使你对韦斯莱用了吐真剂也别想知道我的秘密。"));
}
}
而当魔法部成员管理委员会类(MinistryOfMagicManager)再一次对韦斯莱用“吐真剂”时,由于韦斯莱将凤凰社成员类(PhoenixSociety)的引用完全私有,外部调用即便想要盗取凤凰社成员类(PhoenixSociety)的实现细节也无从下手。他们只能通过公共的方法printPs获得名单,而对于秘密则还是一无所知。
class MinistryOfMagicManager{
//返回魔法部的所有成员
public List<MinistryOfMagic> getList(){
return MajicWorldUnderDemeterPrinciple.initMajicMembers();
}
//输出魔法部成员和凤凰社成员
public void printAllGuys(ArthurWeasley arthur){
//给韦斯莱灌了吐真剂
arthur.printPs();
System.out.println("-------------------------------------");
//获取全部魔法部成员
List<MinistryOfMagic> list = this.getList();
list.forEach(e-> System.out.println("魔法部成员:"+e.getName()));
}
}
我们通过迪米特法则的优化设计挫败了食死徒的阴谋。
结
从设计的角度来看,迪米特法则的核心是降低类与类之间的耦合,减少了不必要的依赖。但是需要注意的是,迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系。这其实是需要根据业务慎重考量的部分。
比如,我现在针对上面的实现加一个需求,编写一个凤凰社管理办公室类,用来记录所有凤凰社成员的秘密(field:secret),这个类是不是就必须要有相应的耦合关系呢?
软件设计的几个原则在实际应用中如何权衡,也是个哲学问题。
关注我,共同进步,每周至少一更。——Wayne