持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情。
依赖倒置原则
定义
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖于抽象 (细节和抽象都应该依赖抽象)
- 面向接口编程而不是面向实现编程
抽象就是抽象类和接口
细节就是可实例化的具体类或实现类,也就是可以new出来的类。
第一点就是松耦合,第二点和第三点就是面向接口编程,不要面向实现编程。
举例
每个程序员都有自己的主攻编程语言。我们使用代码实现一下:
以下例子:Programer就是高层模块、ProgramLanguageJava就是底层模块,此刻高层模块依赖底层模块,且高层模块依赖了细节。那么此例就是不符合依赖倒置原则的。
如果此刻程序员看着Go语言前景较好,改为主攻Go语言,那么此例将不利于扩展。
public class ProgramLanguageJava {
public void java(){
System.out.println("java语言");
}
}
class Programmer {
public void majorIn(ProgramLanguageJava programLanguageJava){
programLanguageJava.java();
}
}
应该这样实现。 细节和抽象都依赖于细节,也就是面向接口编程。
public class Sure {
@Test
public void test() {
ILanguage ljava = new Ljava();
ILanguage lGo = new LGo();
LProgrammer lProgrammer = new LProgrammer();
lProgrammer.work(lGo);
lProgrammer.work(ljava);
}
}
interface ILanguage {
void majorIn();
}
interface IProgrammer {
void work(ILanguage language);
}
class LProgrammer implements IProgrammer {
@Override
public void work(ILanguage language) {
language.majorIn();
}
}
class Ljava implements ILanguage {
@Override
public void majorIn() {
System.out.println("主攻java");
}
}
class LGo implements ILanguage {
@Override
public void majorIn() {
System.out.println("主攻Go");
}
}
依赖注入的方式
构造器注入
将依赖的抽象组合进来,通过构造器给他赋值。
public class Demo03 {
@Test
public void test(){
IProgramL lJava = new LJava();
IProgramL lgo = new Lgo();
ProGrammer proGrammer1 = new ProGrammer(lJava);
proGrammer1.work();
ProGrammer proGrammer2 = new ProGrammer(lgo);
proGrammer2.work();
}
}
interface IProgramL{
void majorIn();
}
class LJava implements IProgramL{
@Override
public void majorIn() {
System.out.println("主修Java");
}
}
class Lgo implements IProgramL{
@Override
public void majorIn() {
System.out.println("主修Go");
}
}
class ProGrammer{
private IProgramL language;
public ProGrammer(IProgramL language) {
this.language = language;
}
void work(){
language.majorIn();
}
}
setter方式
通过setProperty方法初始化赋值。
public class Demo04 {
@Test
public void test() {
IProgramLD4 lJava = new LJavaD4();
IProgramLD4 lgo = new LgoD4();
ProGrammerD4 proGrammer1 = new ProGrammerD4();
proGrammer1.setLanguage(lJava);
proGrammer1.work();
ProGrammerD4 proGrammer2 = new ProGrammerD4();
proGrammer2.setLanguage(lgo);
proGrammer2.work();
}
}
interface IProgramLD4 {
void majorIn();
}
class LJavaD4 implements IProgramLD4 {
@Override
public void majorIn() {
System.out.println("主修Java");
}
}
class LgoD4 implements IProgramLD4 {
@Override
public void majorIn() {
System.out.println("主修Go");
}
}
class ProGrammerD4 {
private IProgramLD4 language;
public void setLanguage(IProgramLD4 language) {
this.language = language;
}
void work() {
language.majorIn();
}
}
接口方法中声明依赖
也就是上面我们举的例子,抽象依赖抽象不能依赖细节。即在抽象类或接口中使用抽象作为方法参数。
接口隔离原则
客户端(调用者)只依赖于它所需要的接口;它需要什么接口就提供什么接口,把不需要的接口剔除掉。
也就是一个类中尽量定义较少的方法,从架构的角度细化接口。
与单一原则比较
两者都是为了提高代码内聚、松耦合
单一原则从业务的角度来约束我们代码,大到模块的设计,小到一个方法的设计,并且通常情况下一个类是不符合单一原则的,也就是单一原则的界限不明确,只要符合业务、并且容易维护即可。
而接口隔离原则是以架构的角度,教我们怎么定义接口,如何避免臃肿的接口,如何合理的细化接口。
迪米特法则
迪米特法则(Law of Demeter )又叫做最少知识原则,也就是说,一个对象应当对其他对象尽可能少的了解。只和朋友说话,不和陌生人说话。英文简写为: LOD。
- 只和存在直接关系的类交流 (和朋友说话)
- 减少对存在直接关系的类的了解 (朋友的事也少打听)
什么样的类可以直接和当前类交互?
- 当前实例本身(this)
- 传入参数
- 实例变量 (类的属性)
- 当前对象所返回的对象(返回参数)
除以上几点,其他情况和当前类直接交互,都违反迪米特法则。
例子
直接相关
有一个学生,想知道自己总成绩(不关心每科成绩),就去问老师
那么这个场景下,成绩与学生不是直接相关的。而下例却在学生类中引用了成绩类,显然违反了迪米特法则。
public class Demo01 {
@Test
public void test() {
Teacher teacher = new Teacher();
Student student = new Student();
student.getSumSource(teacher);
}
}
@Data
@AllArgsConstructor
class Source {
Integer source;
String subject;
}
class Student {
void getSumSource(Teacher teacher) {
Source language = new Source(60, "语文");
Source math = new Source(60, "数学");
Source english = new Source(60, "英语");
List<Source> sources = new ArrayList<>();
sources.add(language);
sources.add(math);
sources.add(english);
teacher.calculate(sources);
}
}
class Teacher {
void calculate(List<Source> list) {
int sum = list.stream().mapToInt(Source::getSource).sum();
System.out.println("总成绩:" + sum);
}
}
那我们做如下修改
public class Demo02 {
@Test
public void test(){
Source language = new Source(60, "语文");
Source math = new Source(60, "数学");
Source english = new Source(60, "英语");
List<Source> sources = new ArrayList<>();
sources.add(language);
sources.add(math);
sources.add(english);
Teacher teacher = new Teacher(sources);
Student student = new Student();
student.getSumSource(teacher);
}
}
@Data
@AllArgsConstructor
class Source {
Integer source;
String subject;
}
class Student {
void getSumSource(Teacher teacher) {
teacher.calculate();
}
}
@Data
@AllArgsConstructor
class Teacher {
List<Source> list;
void calculate() {
int sum = list.stream().mapToInt(Source::getSource).sum();
System.out.println("总成绩:" + sum);
}
}
少知道东西
对朋友的事也少担心,即无需知道存在直接关系的类的内部实现。对于一个类来说,如果一个方法没必要暴露给客户端,那么就必须使用private私有化。
老板让员工造个飞机。老板不关心你咋做的,而且如果此刻造飞机需要添加一个流程,上漆,员工类和老板类都要修改。
public class Demo03 {
@Test
public void test(){
Engineer engineer = new Engineer();
Boss boss = new Boss();
boss.create(engineer);
}
}
//造个飞机
class Engineer{
String eNo;
void draw(){
System.out.println("制图");
}
void components(){
System.out.println("制造零件");
}
void install(){
System.out.println("组装");
}
}
//老板说造个飞机
class Boss{
void create(Engineer engineer){
engineer.draw();
engineer.components();
engineer.install();
}
}
修改
public class Demo04 {
@Test
public void test() {
Engineer engineer = new Engineer();
Boss boss = new Boss();
boss.create(engineer);
}
}
//造个飞机
class Engineer {
private void draw() {
System.out.println("制图");
}
private void components() {
System.out.println("制造零件");
}
private void install() {
System.out.println("组装");
}
public void make() {
this.draw();
this.components();
this.install();
}
}
//老板说造个飞机
class Boss {
void create(Engineer engineer) {
engineer.make();
}
}