前言
最近在复习设计模式,应该有很多小伙伴不理解什么是依赖倒置原则,所以有了写一篇文章的想法。依赖倒置是一个十分重要的原则,让程序的耦合性降低,在Spring中被广泛采用。
基本概念
依赖倒置的基本概念主要有两个:
1、高层模块不应该依赖低层模块,而是应该依赖其抽象。
2、抽象不应该依赖细节,细节应该依赖抽象。
详解
很多小伙伴看到这里绕来绕去的抽象概念就头晕了,不知道到底讲的什么。接下来容我一步步解析。
我想很多小伙伴不理解的原因大概就是不知道高层模块是什么,不知道底层模块是什么,也不知道抽象是什么。接下来通过一个例子带来我对这些的理解。
一个简单的例子
接下来我们用狗吃骨头来说明
我们将骨头类Bone定义如下,只有一个简单的方法,获取骨头的名字。
public class Bone {
public String getBone() {
return "一个普通的骨头";
}
}
Dog类里面有一个eat方法接受Bone作为参数
public class Dog {
public void eat(Bone bone) {
System.out.println("狗在吃" + bone.getBone());
}
}
那么我们狗吃骨头就可以描述成下面这样
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.eat(new Bone());
}
}
//输出结果:狗在吃一个普通的骨头
我们可以很容易的看出这个eat方法的参数写死了只能接受Bone这一种参数类型,
如果这只狗想吃其他类型的骨头呢?有的小伙伴可能会说,把eat方法重载一下,
让他接受其他骨头的参数类型,再定义一个其他骨头的类不就好了嘛?这种做法是可以
但是会带来其他问题。
这种做法的示例如下:
添加其他骨头的类
public class BoneWet {
public String getBone() {
return "一根湿漉漉的骨头";
}
}
添加吃其他骨头的方法
public class Dog {
//原来的方法
public void eat(Bone bone) {
System.out.println("狗在吃" + bone.getBone());
}
//添加的方法
public void eat(BoneWet boneWet) {
System.out.println("狗在吃" + boneWet.getBone());
}
}
狗吃骨头就可以描述成这样子咯
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.eat(new Bone());
d.eat(new BoneWet());
}
}
//狗在吃一个普通的骨头
//狗在吃一根湿漉漉的骨头
这下狗子享福了,可以吃两种类型的骨头。但是此时这条狗子似乎并不满足...
狗子想吃更多种类的骨头,难道我们就不断地定义新骨头类,写新的eat方法吗?
程序员这时候不干了...
这就是刚刚所提到的问题所在。
狗子想吃骨头用图可以表示成这样
image.png
每一种骨头对应一个重载的eat方法
那么有没有办法让我们只用一个重载方法就可以代表所有种类的骨头呢?答案是有的
Java中的接口就可以很好的描述一类有相同方法的类型,这个接口就是这些类的抽象。
我们定义一个接口
public interface BoneInter {
String getBone();
}
再把我们刚刚的两种骨头的方法全部改造一下让他实现我们新定义的接口
public class BoneWet implements BoneInter{
public String getBone() {
return "一根湿漉漉的骨头";
}
}
public class Bone implements BoneInter{
public String getBone() {
return "一个普通的骨头";
}
}
这时我们就可以用BoneInter这个接口代表所有不同种类的骨头了,还记得我们要干嘛吗?把所有eat方法改造成一个方法,既然BoneInter可以表示所有实现这个接口的类那么我么只需要利用这个性质就好了,将其他eat方法全部删掉只保留一个,将eat方法中的参数改为定义的接口
public class Dog {
public void eat(BoneInter bone) {
System.out.println("狗在吃" + bone.getBone());
}
}
再次运行我们的Main方法
输出如下:
狗在吃一个普通的骨头
狗在吃一根湿漉漉的骨头
跟没修改之前一样。
此时的类图结构如下:
image.png
再述概念
在本例中高层模块就是Dog类,因为他是依赖的最顶端,低层模块指的就是不同种类的Bone,抽象就是指的是BoneInter这个接口。刚开始我们的高层模块直接依赖底层模块
后来我们引入了BoneInter这个接口,让高层模块依赖其抽象,而不是低层模块。大费周章我们才把第一个点讲完了。
接下来我们来理解一下第二个点:
我们都知道在Java中ArrayList是继承至List类的。List相对ArrayList来说是较为抽象的,反过来说就是ArrayList类是List类的细节。知道这一点后我们再结合一个篮子的示例来理解。我们通过组合Java的集合类来实现一个篮子类。首先我们有一个抽象篮子
public abstract class AbstractBasket {
protected List<Integer> items;
//protected ArrayList<Integer> items;
}
在上面这个抽象篮子中我注释的这一段就是细节,这个篮子类是抽象的,所以我们不应该依赖于其他类的细节(抽象不应该依赖细节),在本例中就是ArrayList。下面我们来看细节依赖抽象。
public class Basket extends AbstractBasket {
public List<Integer> getAllItem() {
return items;
}
// public ArrayList<Integer> getAllItem() {
// return items;
// }
}
我们将Basket继承至AbstractBasket并返回AbstractBasket类中的items,同样的,上面所注释的那一段代码就是细节的代码,同时此时的Basket类也是属于AbstractBasket的细节,我们这时用的是List这一较为抽象的类就满足第二条(细节应该依赖于抽象)。这样下来我们不仅可以使用ArrayList类来作为Basket类的实现容器,也可以用List类的其他子类作为Basket类的实现容器。
不知道大家到这里有没有明白。
以上都是我的个人理解,如果有错误,欢迎批评指正。









网友评论