草庐IT

设计模式七大原则—依赖倒置原则

坚持 2023-03-28 原文

1.基本介绍

1.1.概念

高层模块不能依赖于一个“具体化、细节化”的低层模块,而是通过一个抽象的“规范/标准”建立两者之间的依赖关系,简言之就是:不依赖于实现,而是依赖于抽象。这里“实现”一词有的地方也称为“细节”,在编码中主要体现的是我们根据业务模型具体自定义的普通类,比如:员工类、商品类等。而其中的“抽象”一词是指定的接口或抽象类。

 

1.2.高层与低层

下面我们通过传统的三层架构作为背景来理解“依赖倒置原则”中的高层与低层的含义。

在分层架构中,高层是相对而言的,对于上面三层架构图中而言最高层是“表示层”,相对于“业务逻辑层”它的高层是“表示层UI”,相对于“数据访问层”它的高层则是“业务逻辑层”。

低层同样也是相对而言的,对于上面三层架构图中而言最低层是“数据访问层”,相对于“业务逻辑层”它的低层是“数据访问层”,相对于“表示层”它的低层则是“业务逻辑层”。

那么简单来说高层就相对于一个使用者,低层就相当于一个被使用者。

 

1.3.依赖倒置原则在分层架构中的体现

在早期比较传统的项目中,三层架构的分层通常都是上图的形式:表示层直接依赖于一个具体实现、非抽象的业务逻辑层,业务逻辑层对下层的依赖同样如此。这种分层实际上在依赖上就是违背了“依赖倒置原则”,因为它都是依赖的一些具体的实现,而非抽象。

那么对于传统的三层架构,使用了“依赖倒置原则”的思想,就可以发挥其中的优势,使其易于维护和扩展。比如说你负责的某个项目的DAL层是使用SqlServer数据库,而需求要变更为Mysql数据库,如果使用了“依赖倒置原则”建立了抽象的规范,那么你就可以在不影响其他层的情况下,单独实现抽象规范就可以进行变更,这样的变更对于“依赖于具体实现”改动是最小的。

1.4.依赖倒置原则体现的缓冲性

例如你写了一个计算统计的程序,其中某个数值的计算比较复杂,你将算法封装成了一个具体实现类,作为比参数变量引入到计算统计的程序。由于第一次写的算法不是很理想,你后面会面临尝试更多新的算法,这就意味着每个新算法的使用,都要去“计算统计程序”中进行协调改动。

如果你在算法设计之初就通过接口或抽象类建立一个算法规范/标准,那么计算统计程序通过接口或抽象类作为参数变量引入算法对象就能起到一个缓冲性,也可以更好的支持算法的迭代,利于程序扩展和优化。


2.通过代码示例说明依赖倒置重要性

下面通过两种代码示例来理解依赖倒置原则以及它的重要性,第一种介绍的是“未使用”依赖倒置原则的代码,并描述在“未使用”依赖倒置原则会出现怎样的利害关系。第二种则是针对第一种出现的问题使用依赖倒置原则对其进行改良,从而体现出依赖倒置原则的优势。

2.1.示例一(依赖实现)

 1    //武器剑
 2     class Sword
 3     {
 4         //攻击的方法
 5         public void Attack()
 6         {
 7             Console.WriteLine("使用剑进行刺杀");
 8         }
 9     }
10 
11     //游戏角色类
12     class GameRole
13     {
14         //使用武器
15         public void UseWeapon(Sword sword)
16         {
17             sword.Attack();
18         }
19     }
20 
21     internal class Program
22     {
23         static void Main(string[] args)
24         {
25             GameRole gameRole = new GameRole();
26             gameRole.UseWeapon(new Sword());
27         }
28     }

上面代码中分别定义了两个类,一个是游戏角色类另一个是剑类,游戏角色类其中有一个“使用武器”的方法,该方法需要传入一个剑类的对象,并调用剑类中的攻击方法。

这个示例使用了我们日常玩电子游戏来作为背景,玩过游戏的朋友应该知道,游戏的更新迭代也是习以为常的事情。如果游戏中使用了以上的代码,并且游戏中的角色使用的武器经常会发生一些改变,那么代码中的游戏角色类要针对每一个新武器新增对应的使用方法。如果无法穷举使用的武器,那我们将会写N个使用新武器的方法。

方式一这种形式的代码,实际上就是在依赖具体的实现,体现在使用武器的方法上,方法总是必须要传入一个具体的武器类(剑、斧头等),而不是依赖一个抽象的标准/规范。我们可以通过示例很直观的看出来,这种依赖具体实现的代码并不适应需求变化。接下来我们则通过使用依赖倒置原则来消除这种“被病”。

 

 2.2.示例二(依赖抽象)

 1     //武器接口
 2     public interface IWeapon
 3     {
 4         void Attack();
 5     }
 6 
 7     //武器剑
 8     class Sword : IWeapon
 9     {
10         public void Attack()
11         {
12             Console.WriteLine("使用剑进行刺杀");
13         }
14     }
15 
16     //斧头
17     class Axe : IWeapon
18     {
19         public void Attack()
20         {
21             Console.WriteLine("使用斧头进行劈砍");
22         }
23     }
24 
25     //游戏角色类
26     class GameRole
27     {
28         //使用武器
29         public void UseWeapon(IWeapon weapon)
30         {
31             weapon.Attack();
32         }
33 
34     }
35 
36 
37     internal class Program
38     {
39         static void Main(string[] args)
40         {
41             GameRole gameRole = new GameRole();
42             gameRole.UseWeapon(new Sword());
43             gameRole.UseWeapon(new Axe());
44         }
45     }

示例二基于依赖倒置原则对示例一进行改良,将多种武器抽象成了一个接口,并且根据武器种类现状创建实现了“武器接口”的实现类(剑、斧头),游戏角色类将多个使用不同武器的方法缩减成一个方法,该方法接收一个“武器接口”类型的实例对象(也就是实现了该接口的类)作为参数。

游戏角色类从原本依赖的具体武器转变成依赖一个抽象武器(依赖具体类转变为依赖接口),从而体现出了从依赖实现到依赖抽象的转变。那么此时不管游戏对角色使用的武器进行新增或删减,“游戏角色类”不用做大量的改动变化,而是将这个变化单独抽离了出去,作为了一个独立的接口。而这个接口不在单单只对游戏角色类服务,还可以用于其他的类(妖怪类、魔兽类等),从而降低了代码的变化性和耦合性,且提高代码的复用性。

 

2.3.示例总结

基于上面的两个示例的分析对比,我们就应该更加谨记“依赖倒置原则”在编码中的重要性,如果我们的代码只是使用面向具体实现编程,那么程序结构上并不能更好的适应变化,更好的扩展、更好的维护,大量的冗余代码会导致程序越来越臃肿。


3.总结

一开始学习“依赖倒置原则”的时候,在明白其中的含义和作用后,往往都在纠结这个“倒置”到底是什么意思,它倒置的是个啥,如果感觉不搞懂总感觉差点意思。经过反复的思考,我个人的理解是:将原本高层对低层依赖具体细节,颠倒反义,转为依赖于抽象。

依赖倒置原则在面向对象编程和框架的设计上都广泛运用,它其中主要的核心思想就是:面向接口编程,而不是面向具体实现编程。使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

有关设计模式七大原则—依赖倒置原则的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  5. ruby-on-rails - 在 ruby​​ .gemspec 文件中,如何指定依赖项的多个版本? - 2

    我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这

  6. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  7. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  8. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  9. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  10. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

随机推荐