DIP 和 SIP 的理解和学习

发布于: 2020 年 06 月 17 日
DIP和SIP的理解和学习

一、DIP(Dependency Inversion Principle)依赖倒置原则

原则内容

  • 高层模块不要依赖低层模块,高层模块合低层模块应该通过抽象来互相依赖。

  • 抽象不要依赖具体实现细节,具体实现细节应该依赖抽象。

总结起来就是:要依赖抽象,不要依赖具体类。

高层模块和低层模块的划分,在调用链上,调用者属于高层,被调用者属于低层。

按照分层和依赖的单向性来讲,子类是上层模块、父类是下层模块

未遵循DIP原则的依赖关系如下图

遵循DIP原则的依赖关系如下图

详解

这个原则可以理解为“针对接口编程,不针对实现编程”,更恰当的说是“针对抽象编程,不针对实现编程”。本原则说明了:不能让高层组件依赖底层组件,并且无论高层组件还是底层组件都应该依赖于抽象。

从类与类之间的关系来讲,类之间的耦合分为:零耦合关系,具体耦合关系和抽象耦合关系,依赖倒转原则要求类和类之间的关系是抽象耦合。

倒置思维

依赖正置就是类之间的依赖就是实实在在的实现类间的依赖,也就是面向实现编程,这也是正常的思维方式,我开宝马就之间依赖宝马。

依赖倒置首先要做的事情是对真实事物抽象,抽象后的产物就是抽象类和接口,然后根据设计要求就产生了抽象之间的依赖,使用抽象依赖代替了事物依赖,这就是“倒置”

依赖倒置到底是倒置了什么?

  • 模块或者包的依赖关系

  • 开发顺序和职责

  • 设计者的思维

如何遵循DIP

  • 变量不可以持有具体类的引用

使用关键字new就会持有具体类的引用,为了避免出现此情况可以使用工厂设计模式

  • 不要让类派生自具体类

为了避免派生自具体类,我们可以派生自接口或者抽象类

  • 不要覆盖基类中已实现的方法

基类中已实现的方法应该由所有子类共享,因此子类不应该覆盖基类中已实现的方法

实现依赖的方法

下图是驾驶汽车的例子。

  • 首先抽象事物,得到了IDriver和ICar两个接口,两个接口分别定义了各自职能。

  • 然后由具体事物完成实现代码,Driver实现了driver职能,BMW、Benz和Rolls实现了run职能

  • 在IDriver中,通过传入ICar接口实现了抽象之间的依赖关系,Driver实现类也传入了ICar接口,驾驶那种类型的Car需要高层模块中声明(这里使用了构造函数传递依赖对象的方式实现依赖)

我们在驾驶汽车的例子上说明下实现依赖的三种方式:

  • 构造函数传递依赖对象

public interface IDriver{
public boolean drive();
}
public class Driver implements IDriver{
private ICar car;
//构造函数注入
public Driver(ICar _car){
this.car = _car;
}
public boolean drive(){
return this.car.run();
}
}

  • Setter方法传递依赖对象

public interface IDriver {
//Setter方式,设置汽车类型
public void setCar(ICar car);
public boolean drive();
}
public class Driver implements IDriver{
private ICar car;
public void setCar(ICar car){
this.car = car;
}
public boolean drive(){
return this.car.run();
}
}

  • 接口申明依赖对象

public interface IDriver{
public boolean drive();
}
public class Driver implements IDriver{
//接口申明依赖对象
private ICar car;
public Driver(ICar _car){
this.car = _car;
}
public boolean drive(){
return this.car.run();
}
}

优点

采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。可以概况为:

  • 高层次决定底层

  • 高层次复用

最佳实践

在项目实践这个规则,只需要遵循以下几点即可:

  • 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备

  • 变量的表面类型尽量是接口或者抽象类

  • 任何类都不应该从具体类派生

  • 尽量不要覆写基类方法

  • 结合里氏替换原则

在大部分面向对象编程语言中大家往往选择抽象工厂模式来解决依赖的问题,实践DIP。

二、好莱坞原则

Don't call me, I'll call you.
别调用我们,我们会调用你

好莱坞原则倒转了层次的依赖关系,这和DIP是一致的,而且很多框架实现DIP都是使用了好莱坞原则,是依赖倒置原则在框架设计中的一种技巧,因此在框架实现的范畴内也可以将依赖倒置原则称为好莱坞原则。

在好莱坞原则下,允许底层组件将自己倒挂到系统上,但是高层组件会决定什么时候和怎么样使用这些底层组件,这就是所谓的“别调用我们,我们会调用你”。

实践

好莱坞原则的核心是:通知/回调

IOC的原理就是基于好莱坞原则,所有的组件都是被动的(Passive),所有的组件初始化和调用都由容器负责。

通过《Head First设计模式》中的一张类图来表述“好莱坞原则”比较容易清晰的理解。

CaffeineBeverage是高层组件,控制了冲泡的算法,只有在需要子类实现某个方法的时候才调用子类。

采用好莱坞原则的设计模式

  • 模板方法

  • 工厂方法

  • 观察者方法

  • ……

DIP在常见框架中的应用

最常见的DIP应用就是spring的Ioc(控制反转),spring的Ioc是通过DI(依赖注入)实现的。

Ioc出现的背景

未使用spring时,创建对象的主动权和创建时机是由类自己把控的,这样就会使得对象间的耦合度高了,例如:A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起

spring Ioc解决方式

spring完成创建对象的工作,Spring创建好对象,然后存储到Ioc容器里面,当需要使用此对象时,Spring就从Ioc容器里面取出要使用的那个对象,然后交给使用方,至于Spring是如何创建那个对象,以及什么时候创建好对象的,使用方无需关心。

Ioc如何实现DIP

IoC是一种创建对象的控制权进行转移设计,以前创建对象的主动权和创建时机是由类自己把控的,而spring将这种权力转移到Ioc容器中。有了Ioc容器之后对象直接的依赖关系变了,由原来的直接依赖改成都依赖Ioc容器。

未使用Ioc的情况:

使用了spring Ioc的情况

DIP、Ioc、DI和Ioc container之间的关系

DIP是一种设计原则

Ioc是DIP的一种实现方式

DI是Ioc的实际实现方法

Ioc container是管理Ioc的一种容器

三、SIP 接口隔离原则

SIP 是什么?

不应该强迫客户端依赖它不需要的方法。

理解

对于官网定义的理解来看,就是调用方需要什么就提供什么样的接口,需要将原始的接口切分,去除不需要的依赖关系,降低因不需要部分的变更引起的调用方同时变更的风险

原始Cache接口

接口包含了常见的get、put和delete方法,同时还有一个reBuild方法,可以重新构建缓存。

基于SIP原则的设计

重新设计后的cache是这样的:

  • 将原来的接口改成抽象类

  • 定义两个接口,一个是常规操作的接口CacheServer;一个构建接口CacheManage

  • 定义一个抽象构建类CacheControlCenter

  • 只有常规操作的cache实现类只需要实现接口CacheServer即可,如果需要自我重新构建的cache则需要在实现接口CacheServer接口的同时继承CacheControlCenter类,实现重新构建的具体实现

发布于: 2020 年 06 月 17 日 阅读数: 10
用户头像

拈香(曾德政)

关注

还未添加个人签名 2018.04.29 加入

还未添加个人简介

评论

发布
暂无评论
DIP和SIP的理解和学习