12 | 软件设计的依赖倒置原则:如何不依赖代码却可以复用它的功能?

在软件开发过程中,我们经常会使用各种编程框架。如果你使用的是 Java,那么你会比较熟悉 Spring、MyBatis 等。事实上,Tomcat、Jetty 这类 Web 容器也可以归类为框架。框架的一个特点是,当开发者使用框架开发一个应用程序时,无需在程序中调用框架的代码,就可以使用框架的功能特性。比如程序不需要调用 Spring 的代码,就可以使用 Spring 的依赖注入,MVC 这些特性,开发出低耦合、高内聚的应用代码。我们的程序更不需要调用 Tomcat 的代码,就可以监听 HTTP 协议端口,处理 HTTP 请求。

这些框架我们每天都在使用,已经司空见惯,所以觉得这种实现理所当然,但是我们停下好好想一想,难道不觉得这很神奇吗?我们自己也写代码,能够做到让其他工程师不调用我们的代码就可以使用我们的代码的功能特性吗?就我观察,大多数开发者是做不到的。那么 Spring、Tomcat 这些框架是如何做到的呢?

依赖倒置原则

我们看下 Spring、Tomcat 这些框架设计的核心关键点,也就是面向对象的基本设计原则之一:依赖倒置原则。

依赖倒置原则是这样的:

高层模块不应该依赖低层模块,二者都应该依赖抽象。

抽象不应该依赖具体实现,具体实现应该依赖抽象。

软件分层设计已经是软件开发者的共识。事实上,最早引入软件分层设计,正是为了建立清晰的软件分层关系,便于高层模块依赖低层模块。一般的应用程序中,策略层会依赖方法层,业务逻辑层会依赖数据存储层。这正是我们日常软件设计开发的常规方式。

那么这种高层模块依赖低层模块的分层依赖方式有什么缺点呢?

一是维护困难,高层模块通常是业务逻辑和策略模型,是一个软件的核心所在。正是高层模块使一个软件区别于其他软件,而低层模块则更多的是技术细节。如果高层模块依赖低层模块,那么就是业务逻辑依赖技术细节,技术细节的改变将影响到业务逻辑,使业务逻辑也不得不做出改变。因为技术细节的改变而影响业务代码的改变,这是不合理的。

二是复用困难,通常越是高层模块,复用的价值越高。但如果高层模块依赖低层模块,那么对高层模块的依赖将会导致对底层模块的连带依赖,使复用变得困难。

事实上,在我们软件开发中,很多地方都使用了依赖倒置原则。我们在 Java 开发中访问数据库,代码并不直接依赖数据库的驱动,而是依赖 JDBC。各种数据库的驱动都实现了 JDBC,当应用程序需要更换数据库的时候,不需要修改任何代码。这正是因为应用代码,高层模块,不依赖数据库驱动,而是依赖抽象 JDBC,而数据库驱动,作为低层模块,也依赖 JDBC。

同样的,Java 开发的 Web 应用也不需要依赖 Tomcat 这样的 Web 容器,只需要依赖 J2EE 规范,Web 应用实现 J2EE 规范的 Servlet 接口,然后把应用程序打包通过 Web 容器启动就可以处理 HTTP 请求了。这个 Web 容器可以是 Tomcat,也可以是 Jetty,任何实现了 J2EE 规范的 Web 容器都可以。同样,高层模块不依赖低层模块,大家都依赖 J2EE 规范。

其他我们熟悉的 MVC 框架,ORM 框架,也都遵循依赖倒置原则。

依赖倒置的关键是接口所有权的倒置

下面,我们进一步了解下依赖倒置原则的设计原理,看看如何在我们的程序设计开发中也能利用依赖倒置原则,开发出更少依赖、更低耦合、更可复用的代码。

这是我们习惯上的层次依赖示例,策略层依赖方法层,方法层依赖工具层。

这样分层依赖的一个潜在问题是,策略层对方法层和工具层是传递依赖的,下面两层的任何改动都会导致策略层的改动,这种传递依赖导致的级联改动可能会导致软件维护过程非常糟糕。

解决办法是利用依赖倒置的设计原则,每个高层模块都为它所需要的服务声明一个抽象接口,而低层模块则实现这些抽象接口,高层模块通过抽象接口使用低层模块。

这样,高层模块就不需要直接依赖低层模块,而变成了低层模块依赖高层模块定义的抽象接口,从而实现了依赖倒置,解决了策略层、方法层、工具层的传递依赖问题。

我们日常的开发通常也要依赖抽象接口,而不是依赖具体实现。比如 Web 开发中,Service 层依赖 DAO 层,并不是直接依赖 DAO 的具体实现,而是依赖 DAO 提供的抽象接口。那么这种依赖是否是依赖倒置呢?其实并不是,依赖倒置原则中,除了具体实现要依赖抽象,最重要的是,抽象是属于谁的抽象。

通常的编程习惯中,低层模块拥有自己的接口,高层模块依赖低层模块提供的接口,比如方法层有自己的接口,策略层依赖方法层的接口;DAO 层定义自己的接口,Service 层依赖 DAO 层定义的接口。

但是按照依赖倒置原则,接口的所有权是被倒置的,也就是说,接口被高层模块定义,高层模块拥有接口,低层模块实现接口。不是高层模块依赖底层模块的接口,而是低层模块依赖高层模块的接口,从而实现依赖关系的倒置。

在上面的依赖层次中,每一层的接口都被高层模块定义,由低层模块实现,高层模块完全不依赖低层模块,即使是低层模块的接口。这样,低层模块的改动不会影响高层模块,高层模块的复用也不会依赖低层模块。对于 Service 和 DAO 这个例子来说,就是 Service 定义接口,DAO 实现接口,这样才符合依赖倒置原则。

使用依赖倒置实现高层模块复用

依赖倒置原则适用于一个类向另一个类发送消息的场景。我们再看一个例子。

Button 按钮控制 Lamp 灯泡,按钮按下的时候,灯泡点亮或者关闭。按照常规的设计思路,我们可能会设计出如下的类图关系,Button 类直接依赖 Lamp 类。

这样设计的问题在于,Button 依赖 Lamp,那么对 Lamp 的任何改动,都可能会使 Button 受到牵连,做出联动的改变。同时,我们也无法重用 Button 类,比如,我们期望通过 Button 控制一个电机的启动或者停止,这种设计显然难以重用 Button,因为我们的 Button 还依赖着 Lamp 呢。

解决之道就是将这个设计中的依赖于实现,重构为依赖于抽象。这里的抽象就是:打开关闭目标对象。至于具体的实现细节,比如开关指令如何产生,目标对象是什么,都不重要。这是重构后的设计。

由 Button 定义一个抽象接口 ButtonServer;在 ButtonServer 中描述抽象:打开、关闭目标对象。由具体的目标对象,比如 Lamp 实现这个接口,从而完成 Button 控制 Lamp 这一功能需求。

通过这样一种依赖倒置,Button 不再依赖 Lamp,而是依赖抽象 ButtonServer,而 Lamp 也依赖 ButtonServer,高层模块和低层模块都依赖抽象。Lamp 的改动不会再影响 Button,而 Button 可以复用控制其他目标对象,比如电机,或者任何由按钮控制的设备,只要这些设备实现 ButtonServer 接口就可以了。

这里再强调一次,抽象接口 ButtonServer 的所有权是倒置的,它不属于底层模块 Lamp,而是属于高层模块 Button。我们从命名上也能看的出来,这正是依赖倒置原则的精髓所在。

这也正好回答了开头提出的问题:如何使其他工程师不调用我们的代码,就能使用我们代码的功能特性?如果我们是 Button 的开发者,那么只要其他工程师的代码实现了我们定义的 ButtonServer 接口,Button 就可以调用他们开发的 Lamp 或者其他任何由按钮控制的设备,使设备代码拥有了按钮功能。设备的代码开发者不需要调用 Button 的代码,就拥有了 Button 的功能,而我们,也不需要关心 Button 会在什么样的设备代码中使用,所有实现 ButtonServer 的设备都可以使用 Button 功能。

所以依赖倒置原则也被称为好莱坞原则:Don’t call me,I will call you. 即不要来调用我,我会调用你。Tomcat、Spring 都是基于这一原则设计出来的,应用程序不需要调用 Tomcat 或者 Spring 这样的框架,而是框架调用应用程序。而实现这一特性的前提就是应用程序必须实现框架的接口规范,比如实现 Servlet 接口。

小结

依赖倒置原则通俗说就是,高层模块不依赖低层模块,而是都依赖抽象接口,这个抽象接口通常是由高层模块定义,低层模块实现。

遵循依赖倒置原则有这样几个编码守则:

1. 应用代码中多使用抽象接口,尽量避免使用那些多变的具体实现类。

2. 不要继承具体类,如果一个类在设计之初不是抽象类,那么尽量不要去继承它。对具体类的继承是一种强依赖关系,维护的时候难以改变。

3. 不要重写(override)包含具体实现的函数。

依赖倒置原则最典型的使用场景就是框架的设计。框架提供框架核心功能,比如 HTTP 处理,MVC 等,并提供一组接口规范,应用程序只需要遵循接口规范编程,就可以被框架调用。程序使用框架的功能,但是不调用框架的代码,而是实现框架的接口,被框架调用,从而框架有更高的可复用性,被应用于各种软件开发中。

我们的代码开发也可以按照依赖倒置原则,参考框架的设计理念,开发出灵活、低耦合、可复用的软件代码。

软件开发有时候像变魔术一样,常常表现出违反常识的特性,让人目眩神晕,而这正是软件编程这门艺术的魅力所在,感受到这种魅力,在自己的软件设计开发中体现出这种魅力,你就迈进了软件高手的大门。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自动控制节水灌溉技术的高低代表着农业现代化的发展状况,灌溉系统自动化水平较低是制约我国高效农业发展的主要原因。本文就此问题研究了单片机控制的滴灌节水灌溉系统,该系统可对不同土壤的湿度进行监控,并按照作物对土壤湿度的要求进行适时、适量灌水,其核心是单片机和PC机构成的控制部分,主要对土壤湿度与灌水量之间的关系、灌溉控制技术及设备系统的硬件、软件编程各个部分进行了深入的研究。 单片机控制部分采用上下位机的形式。下位机硬件部分选用AT89C51单片机为核心,主要由土壤湿度传感器,信号处理电路,显示电路,输出控制电路,故障报警电路等组成,软件选用汇编语言编程。上位机选用586型以上PC机,通过MAX232芯片实现同下位机的电平转换功能,上下位机之间通过串行通信方式进行数据的双向传输,软件选用VB高级编程语言以建立友好的人机界面。系统主要具有以下功能:可在PC机提供的人机对话界面上设置作物要求的土壤湿度相关参数;单片机可将土壤湿度传感器检测到的土壤湿度模拟量转换成数字量,显示于LED显示器上,同时单片机可采用串行通信方式将此湿度值传输到PC机上;PC机通过其内设程序计算出所需的灌水量和灌水时间,且显示于界面上,并将有关的灌水信息反馈给单片机,若需灌水,则单片机系统启动鸣音报警,发出灌水信号,并经放大驱动设备,开启电磁阀进行倒计时定时灌水,若不需灌水,即PC机上显示的灌水量和灌水时间均为0,系统不进行灌水。
智慧农业是一种结合了现代信息技术,包括物联网、大数据、云计算等,对农业生产过程进行智能化管理和监控的新模式。它通过各种传感器和设备采集农业生产中的关键数据,如大气、土壤和水质参数,以及生物生长状态等,实现远程诊断和精准调控。智慧农业的核心价值在于提高农业生产效率,保障食品安全,实现资源的可持续利用,并为农业产业的转型升级提供支持。 智慧农业的实现依赖于多个子系统,包括但不限于设施蔬菜精细化种植管理系统、农业技术资料库、数据采集系统、防伪防串货系统、食品安全与质量追溯系统、应急追溯系统、灾情疫情防控系统、农业工作管理系统、远程诊断系统、监控中心、环境监测系统、智能环境控制系统等。这些系统共同构成了一个综合的信息管理和服务平台,使得农业生产者能够基于数据做出更加科学的决策。 数据采集是智慧农业的基础。通过手工录入、传感器自动采集、移动端录入、条码/RFID扫描录入、拍照录入以及GPS和遥感技术等多种方式,智慧农业系统能够全面收集农业生产过程中的各种数据。这些数据不仅包括环境参数,还涵盖了生长状态、加工保存、检验检疫等环节,为农业生产提供了全面的数据支持。 智慧农业的应用前景广阔,它不仅能够提升农业生产的管理水平,还能够通过各种应用系统,如库房管理、无公害监控、物资管理、成本控制等,为农业生产者提供全面的服务。此外,智慧农业还能够支持政府监管,通过发病报告、投入品报告、死亡报告等,加强农业产品的安全管理和质量控制。 面对智慧农业的建设和发展,存在一些挑战,如投资成本高、生产过程标准化难度大、数据采集和监测的技术难题等。为了克服这些挑战,需要政府、企业和相关机构的共同努力,通过政策支持、技术创新和教育培训等手段,推动智慧农业的健康发展。智慧农业的建设需要明确建设目的,选择合适的系统模块,并制定合理的设备布署方案,以实现农业生产的智能化、精准化和高效化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值