依赖倒置原则
高层模块(调用端)不应该依赖底层模块,两者都应该依赖于抽象。抽象不应该依赖于细节(实现类),细节应该依赖于抽象。
在Java中,抽象指接口或者抽象类,两者都是不能直接被实例化;细节就是实现类,实现接口或者继承抽象类而产生的就是细节,也就是可以加上一个关键字 new 产生的对象。高层模块就是调用端,低层模块就是具体实现类。 依赖倒置原则在 java 中的表现就是,模块间的依赖通过抽象发生,实现类之间不发生直接依赖关系,其依赖关系就是通过接口或者抽象类产生的。如果类与类直接依赖细节,那么就会直接耦合。如此一来,就会同时修改依赖者代码,这样限制了可扩展性。
class Cat { void Cry() { System.out.println("喵喵"); } } class Dog { void Cry() { System.out.println("旺旺"); } } class Animal { void Cry(Cat cat, Dog dog) { cat.Cry(); dog.Cry(); } } class Test{ public static void main(String[] args) { new Animal().Cry(new Cat(),new Dog()); } }
上面这个代码看起来没什么问题,可是如果我们还有别的动物子类时,就又要去更改 Animal类,将其对象作为参数传入Cry方法。而依赖倒置原则 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系。而我们上面这个Demo已经违背了这个原则,下面我们对它进行修改。
interface Dongwu{ void Cry(); } class Cat implements Dongwu{ public void Cry() { System.out.println("喵喵"); } } class Dog implements Dongwu{ public void Cry() { System.out.println("旺旺"); } } class Animal { Dongwu dongwu; public void setDongwu(Dongwu dongwu) { this.dongwu = dongwu; } void Cry() { dongwu.Cry(); } } class Test{ public static void main(String[] args) { Animal animal=new Animal(); animal.setDongwu(new Dog()); animal.Cry(); animal.setDongwu(new Cat()); animal.Cry(); } }
我们增加了一个接口,里面有一个动物的叫声方法,然后让动物类们实现这个接口,然后通过set方法传递依赖对象,这样,如果我们再新增别的动物类,只需要实现相应的接口,而无需再更改我们的管理类 Animal。
迪米特原则
一个软件实体应当少的与其他实体发生相互作用。
这也被称最好知识原则。如果一个系统符合迪米特原则,那么当其中某一个模块发生修改时,就会尽量少的影响其他模块。迪米特原则要求我们在设计系统是,应该尽量减少对象之间的交互。如果两个对象之间不必彼此直接通向,那么这两个对象就不应当发生任何直接的相互作用。如果其中的一个对象需要调用另一个对象的某一个方法,则可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。在将迪米特原则运用到系统设计中时,要注意以下几点:
- 在类的划分上,应当尽量创建松耦合的类。类之间的耦合越低,就越有利于复用。一个处在松耦合中的类一旦被修改,则不会对关联的类造成太大波及。
- 在类的结构上,每一个类都应当尽量降低其成员变量和成员函数的访问权限。
- 在对其他类的引用上,一个对象对其他对象的引用应当降到最低。
举个例子
就像租房子一样,我叫老王,准备租房子,然后找中介,中介和房东谈价格,我们和中介谈价格,如果我们想自己和房东联系,这种事情肯定是要通过中介介绍了,或者别的途径。
Demo如下
//租房者:老王类 //inform 通知方法 class LaoWang { private Zhongjie zhongjie; public Zhongjie getZhongjie(){ return zhongjie; } public void setZhongjie(Zhongjie zhongjie) { this.zhongjie = zhongjie; } public void inform(){ zhongjie.inform(); } } //中介类 //inform 通知方法 //setLandlord 接收房东消息 class Zhongjie{ public void inform(){ System.out.println("通知房东"); } public void getLanged(){ new Landlord().inform(); } } //房东类 //inform 通知方法 class Landlord{ public void inform(){ System.out.println("收到,可以租"); } } class Renting{ public static void main(String[] args) { LaoWang laoWang=new LaoWang(); //老王通知中介 laoWang.inform(); //传入房东对象 laoWang.setZhongjie(new Zhongjie()); //由中介去通知房东 laoWang.getZhongjie().inform(); //接收房东消息 laoWang.getZhongjie().getLanged(); } }
这样,老王和房东之间就没有任何联系,避免了耦合度过高。
我们还可以将上面的Demo再次更改,相当和依赖倒转原则结合。因为一般租房会看好多房子,所以房东也各不相同,这时候就可以将房东抽成一个抽象类,具体的房东实现房东抽象方法即可,这样的方式,和老王通信的就是房东的抽象父类,和具体房东没有关系。
public abstract class Lease { abstract void inform(); } //租房者:老王类 //inform 通知方法 class LaoWang { private Zhongjie zhongjie; private Lease lease; public void setLease(Lease lease) { this.lease = lease; } public void setZhongjie(Zhongjie zhongjie) { this.zhongjie = zhongjie; } public void inform() { zhongjie.inform(); lease.inform(); } } //中介类 //inform 通知方法 class Zhongjie { public void inform() { System.out.println("通知房东"); } } //Landlord_X 房东X //inform 通知方法 class Landlord_X extends Lease { public void inform() { System.out.println("收到,可以租"); } } class Renting{ public static void main(String[] args) { LaoWang laoWang=new LaoWang(); laoWang.setLease(new Landlord_X()); laoWang.setZhongjie(new Zhongjie()); laoWang.inform(); } }
接口隔离原则
一个类对另一个类的依赖应该建立在最小的接口上
建立单一接口,不要建立庞大臃肿的接口:尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图建立一个一个很庞大的接口供所有依赖他的类调用。采取接口隔离原则对接口进行约束时,要注意以下几点:
接口尽量小,但是要有限度。对接口进行细化可以提高程序设计的灵活性;但是如果过小,则会造成接口数量过多,使设计复杂化。所以,一定要适度。
为依赖接口的类定制服务,只暴露给调用的类他需要的方法,他不需要的方法则隐藏起来,只有专注的为一个模块提供定制服务,才能建立最小的依赖关系。
为提高内聚,减少对外交互。接口方法尽量少用public 修饰。接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也越少。
Demo
//color 颜色 //taste 口感 //hardness 硬度 // small气味 interface Frits { void color(); void taste(); void hardness(); void small(); } class apple implements Frits{ @Override public void color() { } @Override public void taste() { } @Override public void hardness() { } @Override public void small() { } }
定义了一个水果接口,里面有水果的各项方法,如果我们还有别的方法,那么这个接口必然会受到多次修改。所以我们可以对其进行分隔,比如外观为一类,内在为一类,结果如下
//Facade 外观 interface Facade { void color(); void hardness(); } //Inherent 内在 interface Inherent { void taste(); void small(); } class banana implements Facade,Inherent{ @Override public void taste() { } @Override public void small() { } @Override public void color() { } @Override public void hardness() { } }
接口是我们设计时对外提供的契约,通过分散定义多个 接口,可以预防未来变更的扩散,提高系统的灵活性和可维护性。