让我们从最后一个 SOLID 原则开始吧,即依赖倒置原则(Dependency Inversion Principle,简称 DIP)(不要和依赖注入Dependency Injection ,DI 弄混淆了)。这个原则所说的是高级模块不应该依赖具象的低级模块,它们都应该依赖相应模块的抽象层。

我仍将使用自行车的示例来尝试给你解释这个原则。首选看下这个 Bike 接口:

interface Bike {void pedal()void backPedal()
}

MountainBikeClassicBike 这两个类实现了上面的接口:

// 山地车
class MountainBike implements Bike {override void pedal() {// complex code that computes the inner workings of what happens // when pedalling on a mountain bike, which includes taking into // account the gear in which the bike currently is.}override void backPedal() {// complex code that computes what happens when we back pedal    // on a mountain bike, which is that you pedal in the wrong   // direction with no discernible effect on the bike}
}// 传统自行车
class ClassicBike implements Bike {override void pedal() {// the same as for the mountain bike with the distinction that // there is a single gear on a classic bike}override void backPedal() {// complex code that actually triggers the brake function on the     // bike}
}

正如你所看到的,踩脚踏板(pedal)会让自行车向前行驶,但是山地车 MountainBike 因为有多个齿轮,所以它的 pedal 会更加复杂。另外,向后踩脚踏板(back pedal)时,山地车不会做任何事,而传统自行车 ClassicBike 则会触发刹车操作。

我之所以在每个方法的注释中都有提到“complex code”,是因为我想指出我们应该把上述代码移动到不同的模块中。我们这样做是为了简化自行车类以及遵循单一职责原则(自行车类不应该担起在你向前或向后踩脚踏板时究竟发生了什么的计算工作,它们应该处理有关自行车的更高级别的事情)。

为了做到这一点,我们将为每种类型的 pedalling 创建一些行为类。

class MountainBikePedalBehaviour {void pedal() {//complex code}
}class MountainBikeBackPedalBehaviour {void backPedal() {// complex code}
}class ClassicBikePedalBehaviour {void pedal() {// complex code}
}class ClassicBikeBackPedalBehaviour {void backPedal() {// complex code}
}

然后像下面这样使用这些类:

// 山地车
class MountainBike implements Bike {override void pedal() {var pedalBehaviour = new MountainBikePedalBehaviour()pedalBehaviour.pedal()}override void backPedal() {var backPedalBehaviour = new MountainBikeBackPedalBehaviour()backPedalBehaviour.backPedal()}
}// 传统自行车
class ClassicBike implements Bike {override void pedal() {var pedalBehaviour = new ClassicBikePedalBehaviour()pedalBehaviour.pedal()}override void backPedal() {var backPedalBehaviour = new ClassicBikeBackPedalBehaviour()backPedalBehaviour.backPedal()}
}

这个时候,我们可以很清楚地看到高级模块 MountainBike 依赖于某些具体的低级模块 MountainBikePedalBehaviourMountainBikeBackPedalBehaviourClassicBike 以及它的低级模块同样如此。根据依赖倒置原则,高级模块和低级模块都应该依赖抽象。为此,我们需要以下接口:

interface PedalBehaviour {void pedal()
}interface BackPedalBehaviour {void backPedal()
}

除了需要实现上面的接口外,行为类的代码与之前无异:

class MountainBikePedalBehaviour implements PedalBehaviour {override void pedal() {// same as before}
}

剩下的其他行为类同上。

现在我们需要一种方法将 PedalBehaviourBackPedalBehaviour 传递给 MountainBikeClassicBike 类。我们可以选择在构造方法、pedal()pedalBack() 中完成这件事。本例中,我们使用构造方法。

class MountainBike implements Bike {PedalBehaviour pedalBehaviour;BackPedalBehaviour backPedalBehaviour;public MountainBike(PedalBehaviour pedalBehaviour,BackPedalBehaviour backPedalBehaviour) {this.pedalBehaviour = pedalBehaviour;this.backPedalBehaviour = backPedalBehaviour;}override void pedal() {pedalBehaviour.pedal();}override void backPedal() {backPedalBehaviour.backPedal();}
}

ClassicBike 类同上。

我们的高级模块(MountainBikeClassicBike)不再依赖于具体的低级模块,而是依赖于抽象的 PedalBehaviourBackPedalBehaviour

在我们的例子中,我们应用的主模块可能看起来向下面这样:

class MainModule {MountainBike mountainBike;ClassicBike classicBike;MountainBikePedalBehaviour mountainBikePedalBehaviour;ClassicBikePedalBehaviour classicBikePedalBehaviour;MountainBikeBackPedalBehaviour mountainBikeBackPedalBehaviour;ClassicBikeBackPedalBehaviour classicBikeBackPedalBehaviour;public MainModule() {mountainBikePedalBehaviour = new MountainBikePedalBehaviour();mountainBikeBackPedalBehaviour = new MountainBikeBackPedalBehaviour();mountainBike = new MountainBike(mountainBikePedalBehaviour,   mountainBikeBackPedalBehaviour);classicBikePedalBehaviour = new ClassicBikePedalBehaviour();classicBikeBackPedalBehaviour = new ClassicBikeBackPedalBehaviour();classicBike = new ClassicBike(classicBikePedalBehaviour,classicBikeBackPedalBehaviour);}public void pedalBikes() {mountainBike.pedal()classicBike.pedal()}public void backPedalBikes() {mountainBike.backPedal();classicBike.backPedal();}
}

可以看到,我们的 MainModule 依赖了具体的低级模块而不是抽象层。我们可以通过向构造方法中传递依赖来改善这种情况:

public MainModule(Bike mountainBike, Bike classicBike, PedalBehaviour mBikePB, BackPedalBehaviour mBikeBPB, PedalBehaviour cBikePB, BackPedalBehaviour cBikeBPB)...

现在,MainModule 部分依赖了抽象层,部分依赖了低级模块,这些低级模块也依赖了那些抽象层。所有这些模块之间的关系不再依赖于实现细节。

在我们到达应用程序中的最高模块之前,为了尽可能地延迟一个具体类的实例化,我们通常要靠依赖注入和实现了依赖注入的框架。你可以在 这里 找到更多有关依赖注入的信息。我们可以将依赖注入视为帮助我们实现依赖倒置的工具。我们不断地向依赖链中传递依赖关系以避免具体类的实例化。

那么为什么要经历这一切呢?不依赖于具象的一个优点就是我们可以模拟一个类,从而使测试更容易进行。我们来看一个简单的例子。

interface Network {public String getServerResponse(URL serverURL);
}class NetworkRequestHandler implements Network {override public String getServerResponse(URL serverURL) {// network code implementation}
}

假设我们还有一个 NetworkManager 类,它有一个公共方法,通过使用一个 Network 的实例返回服务器响应:

public String getResponse(Network networkRequestHandler, URL url) {return networkRequestHandler.getServerResponse(url)
}

因为这样的代码结构,我们可以测试代码如何处理来自服务器的“404”响应。为此,我们将创 NetworkRequestHandler的模拟版本。我们之所以可以这么做,是因为 NetworkManager 依赖于抽象层,即 Network,而不是某个具体的 NetworkRequestHandler

class Mock404 implements Network {override public String getServerResponse(URL serverURL) {return "404"}
}

通过调用 getResponse 方法,传递 Mock404 类的实例,我们可以很容易地测试我们期望的行为。像 Mockito 这样的模拟库可以帮助你模拟某些类,而无需编写单独的类来执行此操作。

除了易于测试,我们的应用在多变情景下也能应对自如。因为模块之间的关系是基于抽象的,我们可以更改具体模块的实现,而无需大范围地更改代码。

最后同样重要的是这会让事情变得更简单。如果你有留意自行车的示例,你会发现 MountainBikeClassicBike 类非常相似。这就意味着我们不再需要单独的类了。我们可以创建一个简单的实现了 Bike 接口的类 GenericBike,然后山地车和传统自行车的实例化就像下面这样:

GenericBike mountainBike = new GenericBike(mbPedalB, mbBackPedalB);
GenericBike classicBike = new GenericBike(cbPedalB, cbBackPedalB);

我们减少了一半数量的具体自行车类的实现,这意味着我们的代码更容易管理。

总结

所有这些原则可能看起来有点矫枉过正,你可能会排斥它们。在很长的一段时间里,我和你一样。随着时间的推移,我开始逐渐把我的代码向增强可测试性和更易于维护的方向转变。渐渐地,我开始这样来思考事情:“如果只有一种方法可以把两个部分的内容分开,并将其放在不同的类中,以便我能……”。通常,答案是的确存在这样的一种方法,并且别人已经实现过了。大多数时候,这种方法都受到 SOLID 原则的启发。当然,紧迫的工期和其他现实生活中的因素可能会不允许你遵守所有这些原则。虽然很难 100% 实现 SOLID 原则,但是有比没有强吧。也许你可以尝试只在那些当需求变更时最容易受影响的部分遵守这些原则。你不必过分遵循它们,可以把这些原则视为你提高代码质量的指南。如果你不得不需要制作一个快速原型或者验证一个概念应用的可行性,那么你没有必要尽力去搭一个最佳架构。SOLID更像是一个长期策略,对于必须经得起时间考验的软件非常有用。

在这篇由三部分组成的文章中,我试图给你展示了一些有关 SOLID的我发现比较有趣的东西。关于 SOLID,还有很多看法和解释,为了更好地理解和从多个角度获取知识,请多阅读些其他文章。

我希望这篇文章对你有所帮助。

……

如果你还没有看过另两个部分,这里是它们的链接,第1部分 和 第2部分 。如果你喜欢这篇文章,你可以在我们的 官方站点 上找到更多信息。

什么是SOLID原则(第3部分)相关推荐

  1. 独家 | Python中的SOLID原则(附链接)

    作者:Mattia Cinelli翻译:朱启轩校对:欧阳锦本文约3500字,建议阅读15分钟本文通过一些Python示例代码介绍了可以提高代码可靠性的SOLID编码准则. 标签:数据结构,编程,数据科 ...

  2. 每个Web开发者都应该知道的SOLID原则

    原创: 前端之巅 前端之巅 10月20日 作者|Chidume Nnamdi 编辑|谢丽 面向对象的编程并不能防止难以理解或不可维护的程序.因此,Robert C. Martin 制定了五项指导原则, ...

  3. 设计模式之SOLID原则再回首

        本科阶段学过设计模式,那时对设计模式的五大原则--SOLID原则的概念与理解还是比较模糊,此时过去了2年时间,在学习<高级软件工程>课程中老师又提到了设计模式,课程中还详细讨论了五 ...

  4. 正反案例介绍SOLID原则

    一.概述 SOLID五大原则使我们能够管理解决大多数软件设计问题.由Robert C. Martin在20世纪90年代编写了这些原则.这些原则为我们提供了从紧耦合的代码和少量封装转变为适当松耦合和封装 ...

  5. SOLID 原则的可靠指南

    山姆·米灵顿 读完需要 7 分钟 速读仅需 3 分钟 山姆是牛津大学的一名软件开发人员,目前在生物信息学领域工作.山姆的专长领域是 Java,他每天都在为生物学研究编写多线程分析工具.他是一位热情的程 ...

  6. 实践GoF的23的设计模式:SOLID原则(下)

    本文分享自华为云社区<实践GoF的23的设计模式:SOLID原则(下)>,作者: 雷电与骤雨. 在<实践GoF的23种设计模式:SOLID原则(上)>中,主要讲了SOLID原则 ...

  7. 实践GoF的23种设计模式:SOLID原则(上)

    本文分享自华为云社区<实践GoF的23种设计模式:SOLID原则(上)>,作者:元闰子. 前言 从1995年GoF提出23种设计模式到现在,25年过去了,设计模式依旧是软件领域的热门话题. ...

  8. 用 SOLID 原则保驾 React 组件开发

    概述 本世纪初,美国计算机专家和作者 Robert Cecil Martin 针对 OOP 编程,提出了可以很好配合的五个独立模式:后由重构等领域的专家 Michael Feathers 根据其首字母 ...

  9. TypeScript 中的 SOLID 原则

    下面的文章解释了正确使用 TypeScrip的 SOLID原则. 原文地址:https://samueleresca.net/2016/08/solid-principles-using-typesc ...

最新文章

  1. 傅里叶帮我看看,谁在照射我?
  2. SQL(基于MySQL)——LIMIT用法
  3. MQTT协议通俗讲解
  4. java8-新特性default
  5. SpringCloud系列十三:Feign对继承、压缩、日志的支持以及构造多参数请求
  6. 新款iPhone SE来了,从二手市场保值率来看值不值得买?
  7. 【VS开发】VS2010中导入ActiveX控件
  8. 碱度控制化学品行业调研报告 - 市场现状分析与发展前景预测
  9. cupsd进程_Linux进程基础
  10. 在.h文件和.m文件里使用import指令有何区别?
  11. php调用酷狗音乐APi
  12. 网络聊天室的分析与实现
  13. 盘点火影中写轮眼谁最强
  14. 软件测试需要什么思维,做软件测试需要学习什么
  15. 逐梦旅程(著:毛星云)---学习笔记第三章
  16. cent7虚拟机镜像_centos7.3系统下载
  17. 竞价推广排名与自然排名的优缺点?
  18. 什么是通货膨胀(Inflation)
  19. php编写函数6,【函数分享】每日PHP函数分享(2021-2-6)
  20. 嗓子不舒服怎么办?咽干咽痛痰多咳嗽怎么办?

热门文章

  1. java.utilDate和java.sql.Date
  2. Spring Boot集成Swagger导入YApi@无界编程
  3. web集群时session同步的3种方法
  4. C#精髓【月儿原创】第一讲 使用垃圾回收器
  5. 打开,保存文件框的文本溢出排查
  6. 吴恩达老师深度学习视频课笔记:单隐含层神经网络公式推导及C++实现(二分类)
  7. OpenCV中与matlab中相对应的函数
  8. Linux中bashrc河bash_profile
  9. c++ include 路径_头文件中,#include使用引号“”和尖括号lt;gt;有什么区别?
  10. python pandas_Python库Pandas数据可视化实战案例