依赖倒置原则定义
高层模块不应该依赖底层模块,二者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象。
因此需要做到针对接口编程,不要针对实现编程
优点
可以减少类之间的耦合性,提高系统的稳定性,提高代码的可读性和维护性,可降低修改程序所造成的风险
代码讲解
比如我喜欢吃苹果,喜欢吃香蕉,写一个程序来实现这一例子
先创建一个类表示我喜欢的水果
public void likeApple(){
System.out.println("Jack喜欢吃苹果");
}
public void likeBanana(){
System.out.println("Jack喜欢吃香蕉");
}
}
创建一个测试类
public static void main(String[] args) {
Jack jack=new Jack();
jack.likeApple();
jack.likeBanana();
}
}
测试结果显示了Jack喜欢吃的水果
测试类作为应用层为高层,现在应用层的调用是依赖于底层的实现
新的需求
jack突然喜欢上西瓜了,天天抱着瓜吃,现在业务需要扩展了,我们需要在底层实现区增加一个打印jack喜欢吃西瓜的一个方法
public void likeApple(){
System.out.println("Jack喜欢吃苹果");
}
public void likeBanana(){
System.out.println("Jack喜欢吃香蕉");
}
public void likeWatermelon(){
System.out.println("Jack喜欢吃西瓜");
}
}
在测试类中我们需要去使用才可以实现
public static void main(String[] args) {
Jack jack=new Jack();
jack.likeApple();
jack.likeBanana();
jack.likeBanana();
}
}
测试结果实现了扩展
这种方式的实现就会是我们这样的频繁的修改,这种修改代码的同时也可能会造成意外的风险,现在来优化这种面向实现的编程
优化
首先新建一个接口,做到面向借口编程,这个接口提供了一个方法获取喜欢吃的水果类型
void likeEat();
}
为每中水果创建一个实体类去实现水果接口并继承水果接口中的方法
苹果类
@Override
public void likeEat() {
System.out.println("Jack喜欢吃苹果");
}
}
香蕉类
@Override
public void likeEat() {
System.out.println("Jack喜欢吃香蕉");
}
}
然后jack这个实现类定义一个通用的方法,参数是水果接口的实现类对象然后调用接口的方法
// public void likeApple(){
// System.out.println("Jack喜欢吃苹果");
// }
// public void likeBanana(){
// System.out.println("Jack喜欢吃香蕉");
// }
// public void likeWatermelon(){
// System.out.println("Jack喜欢吃西瓜");
// }
public void likeEat(Fruit fruit){
fruit.likeEat();
}
}
应用层只需要给方法传入一个对象即可
public static void main(String[] args) {
Jack jack=new Jack();
// jack.likeApple();
// jack.likeBanana();
// jack.likeBanana();
jack.likeEat(new Apple());
jack.likeEat(new Banana());
}
}
测试结果
现在来完成新的需求,也就是加入西瓜这个类
@Override
public void likeEat() {
System.out.println("Jack喜欢吃西瓜");
}
}
然后在应用层给方法传入西瓜这个参数即可
public static void main(String[] args) {
Jack jack=new Jack();
// jack.likeApple();
// jack.likeBanana();
// jack.likeBanana();
jack.likeEat(new Apple());
jack.likeEat(new Banana());
jack.likeEat(new Watermelon());
}
}
测试结果
这么优化就是我们只需要修改应用层的代码和新增类去扩展功能,不再去修改底层的代码,应用层只需要传入需要的参数即可。
扩展
基于构造器的方式依赖注入
public class Jack {
// public void likeApple(){
// System.out.println("Jack喜欢吃苹果");
// }
// public void likeBanana(){
// System.out.println("Jack喜欢吃香蕉");
// }
// public void likeWatermelon(){
// System.out.println("Jack喜欢吃西瓜");
// }
// public void likeEat(Fruit fruit){
// fruit.likeEat();
// }
private Fruit fruit;
public Jack(Fruit fruit) {
this.fruit = fruit;
}
public void likeEat(){
fruit.likeEat();
}
}
应用层
public static void main(String[] args) {
// jack.likeApple();
// jack.likeBanana();
// jack.likeBanana();
// jack.likeEat(new Apple());
// jack.likeEat(new Banana());
// jack.likeEat(new Watermelon());
Jack jack=new Jack(new Apple());
jack.likeEat();
}
}
测试结果
基于构造器的注入 ,我们在new对象的时候只需要传入相应的类再调用方法即可,但是基于构造器注入,我们每次使用都需要创建一个实例,如果jack是全局的单列,这种方式就不太好了
优化构造器注入的问题
由于单例我们不可以反复的new所以给实现类一个set方法
// public void likeApple(){
// System.out.println("Jack喜欢吃苹果");
// }
// public void likeBanana(){
// System.out.println("Jack喜欢吃香蕉");
// }
// public void likeWatermelon(){
// System.out.println("Jack喜欢吃西瓜");
// }
// public void likeEat(Fruit fruit){
// fruit.likeEat();
// }
private Fruit fruit;
// public Jack(Fruit fruit) {
// this.fruit = fruit;
// }
public void setFruit(Fruit fruit) {
this.fruit = fruit;
}
public void likeEat(){
fruit.likeEat();
}
}
应用层
public class Test {
public static void main(String[] args) {
// jack.likeApple();
// jack.likeBanana();
// jack.likeBanana();
// jack.likeEat(new Apple());
// jack.likeEat(new Banana());
// jack.likeEat(new Watermelon());
// Jack jack=new Jack(new Apple());
// jack.likeEat();
Jack jack=new Jack();
jack.setFruit(new Apple());
jack.likeEat();
}
}
测试结果
当我们需要什么功能时再往对象里set即可
类图
可以看出具体的水果类都实现了上一层的接口,我们应用层只需要调用接口,具体的实现我们只需要在应用层将参数传入(依赖)因此就实现了面向接口的编程,一个阅读性好的框架可以更稳定。