些年搞不懂的高深术语——依赖倒置•控制反转•依赖注入•面向接口编程 📅 2026/7/2 2:38:31 那些年空气中仿佛还能闻到汉唐盛世的余韵因此你决不允许自己的脸上有油光时刻保持活力。然而你一定曾为这些“高深术语”感到过困扰——依赖倒置•控制反转•依赖注入•面向接口编程。也许时至今日你仍对它们一知半解。不过就在今天这一切都将彻底改变我将带领你以一种全新的高清视角进入奇妙的编程世界领略涵泳在这些“高深术语”中的活泼泼的地气以及翩跹于青萍之末的云水禅心。·内聚内聚通俗的来讲就是自己的东西自己保管自己的事情自己做。经典理论告诉我们程序的两大要素一个是数据data一个是操作opration。而 PASCAL之父Nicklaus Wirth则进一步提出了“程序 数据结构 算法”的著名公式。虽然提法上有所差异但是其根本内涵却是一致的微妙的差别在于“数据 操作”是微观的视域“数据结构 算法”则是中观的视域。而在宏观的视域下我认为“程序 对象 消息”。对象是什么对象就是保管好自己的东西做好自己的事情的程序模块——这就是内聚传统的面向过程编程方法由于割裂了数据结构和算法使得软件的内聚性普遍低迷曾一度引发了软件危机。试想大家都自己的东西不好好保管自己的事情也不好好做不引发危机才怪呢当然对象的内聚只是内聚的一个层次在不同的尺度下其实都有内聚的要求比如方法也要讲内聚架构也要讲内聚。《周易·彖传》中讲“乾道变化各正性命保合太和乃利贞”就是要求每一个个体因循着各自的禀赋而努力成就各自的品性然后各自保全彼此和合最终达成宇宙的完满状态。《论语·宪问》中子路问君子。子曰“修己以敬。”曰“如斯而已乎”曰“修己以安人”更是明确的教导我们要不断提高自身的内聚性最大限度地减少给他人造成的麻烦从而达到安人、安百姓、安天下的目标。我想成长的过程就是一个不断提升内聚的过程。“自己的东西自己保管自己的事情自己做”这些孩提时代的教诲放到今天仍能让不少“大人”脸红不已。太多的人保管不好自己的“东西”保管不好自己的身体保管不好自己的婚姻更保管不好自己如蛛丝般震颤飘荡的狂乱的心。至于做好自己的事情则更是惘然甚至很多人连自己的事情是什么都搞不清楚因此浑浑噩噩饱食终日。内聚是一个值得我们好好反思的问题。·依赖·耦合在面向对象编程中对象自身是内聚的是保管好自己的数据完成好自己的操作的而对外界呈现出自己的状态和行为。但是没有绝对的自力更生对外开放也是必要的一个对象往往需要跟其他对象打交道既包括获知其他对象的状态也包括仰赖其他对象的行为而一旦这样的事情发生时我们便称该对象依赖于另一对象。只要两个对象之间存在一方依赖一方的关系那么我们就称这两个对象之间存在耦合。 比如妈妈和baby妈妈要随时关注baby的睡、醒、困、哭、尿等等状态baby则要仰赖妈妈的喂奶、哄睡、换纸尿裤等行为从程序的意义上说二者互相依赖因此也存在耦合。首先要说耦合是必要的。我们来看以下这个实验。【王阳明与山中之花】View Code由于王阳明这个对象不依赖山花这个对象又没有其他的方式来获知山花的盛开状态所以他要么选择不说要么瞎说但不说编译是通不过而瞎说作为王阳明来讲也是通不过的所以这个系统是无法成立的。要想系统成立必须要这样写public bool AdmireFlowers() { returnflower.IsBloomed; ; }无论这个山花对象是怎么来的作为参数传入还是作为属性设置、还是在内部构造出来总之王阳明与山花之间发生了依赖二者之间产生了耦合。 当然这是一个很浅显的问题。有趣的是王阳明对此事的看法“你未看花时花与你同寂你来看花花于你则一时分明起来。可见心外无物”王阳明讲的是对的“心外无物”翻译技术语言是这样的不存在耦合的两个对象必然拿不到对方的引用·耦合度·解耦和耦合的程度就是耦合度也就是双方依赖的程度。上文所说的妈妈和baby就是强耦合。而你跟快递小哥之间则是弱耦合。一般来说耦合度过高并不是一件好事。就拿作为IT精英的你来说吧上级随时敦促你的工作进度新手频繁地需要你指导问题隔三差五还需要参加酒局饭局然后还要天天看领导的脸色、关注老婆的心情然后你还要关注代码中的bug 、bug、bug和需求的变化、变化、变化都够焦头烂额了还猝不及防的要关注眼睛、颈椎、前列腺和头发的状态然后你再炒个股这些加起来大概就是个强耦合了。从某种意义上来说耦合天生就与自由为敌无论是其他对象依赖于你还是你依赖其他对象。比如有人嗜烟、酗酒你有多依赖它们就有多不自由比如有人家里生了七八个娃还有年迈的父母、岳父母他们有多依赖你你就有多不自由。所以老子这样讲“五音令人耳聋五色令人目盲驰骋狩猎令人心发狂难得之货令人行妨。”卢梭也是不无悲凉的说“人生而自由却又无往而不在枷锁中”。因此要想自由就必须要降低耦合而这个过程就叫做解耦和。·依赖倒置Dependence Inversion Principle解耦和最重要的原则就是依赖倒置原则高层模块不应该依赖底层模块他们都应该依赖抽象。抽象不应该依赖于细节细节应该依赖于抽象。《资本论》中都曾阐释依赖倒转原则——在商品经济的萌芽时期出现了物物交换。假设你要买一个IPhone卖IPhone的老板让你拿一头猪跟他换可是你并没有养猪你只会编程。所以你找到一位养猪户说给他做一个养猪的APP来换他一头猪他说换猪可以但是得用一条金项链来换——所以这里就出现了一连串的对象依赖从而造成了严重的耦合灾难。解决这个问题的最好的办法就是买卖双发都依赖于抽象——也就是货币——来进行交换这样一来耦合度就大为降低了。再举一个编程中的依赖倒置的例子。我们知道在通信中消息的收发和消息的处理往往密不可分。就一般的通信框架而言消息的收发通常是已经实现了的而消息的处理则是需要用户来自定义完成的。先看一个正向依赖的例子轻量级通信引擎StriveEngine。tcpServerEngine是StriveEngine.dll提供通信引擎它发布有一个MessageReceived事件。假设我定义了一个CustomizeHandler类来用于消息处理那么CustomizeHandler的内部需要预定tcpServerEngine的MessageReceived事件因此customizeHandler依赖于tcpServerEngine这就是一个普通的依赖关系也就是高层模块依赖于低层模块。而ESFramework通信框架则应用了依赖倒转原则。ESFramework定义了一个IcustomizeHandler接口用户在进行消息处理时实现该接口然后将其注入到rapidPassiveEngine客户端通信引擎之中。View Code很明显相比于上一个例子这里的依赖关系变成了rapidPassiveEngine依赖于customizeHandler也就是说依赖关系倒置了过来上层模块不再依赖于底层模块而是它们共同依赖于抽象。rapidPassiveEngine依赖的是IcustomizeHandler接口类型的参数customizeHandler同样是以实现的接口的方式依赖于IcustomizeHandler——这就是一个依赖倒置的典范。·控制反转Inversion of Control控制反转跟依赖倒置是如出一辙的两个概念当存在依赖倒置的时候往往也存在着控制反转。但是控制反转也有自己的独特内涵。首先我们要区分两个角色server 跟 Client也就是服务方和客户方。提供服务端的一方称为服务方请求服务的一方称为客户方。我们最熟悉的例子就是分布式应用的C/S架构服务端和客户端。其实除此之外C/S关系处处可见。比如在TCP/IP协议栈中我们知道每层协议为上一层提供服务那么这里就是一个C/S关系。当我们使用开发框架时开发框架就是作为服务方而我们自己编写的业务应用就是客户方。当Client调用server时这个叫做一般的控制而当server调用Client时就是我们所说的控制反转同时我们也将这个调用称为“回调”。控制反转跟依赖倒置都是一种编程思想依赖倒置着眼于调用的形式而控制反转则着眼于程序流程的控制权。一般来说程序的控制权属于Client而一旦控制权交到server就叫控制反转。比如你去下馆子你是Client餐馆是server。你点菜餐馆负责做菜程序流程的控制权属于Client而如果你去自助餐厅程序流程的控制权就转到server了也就是控制反转。控制反转的思想体现在诸多领域。比如事件的发布/ 订阅就是一种控制反转GOF设计模式中也多处体现了控制反转比如典型的模板方法模式等。而开发框架则是控制反转思想应用的集中体现。比如之前所举的ESFramework通信框架的例子通信引擎回调用户自定义的消息处理器这就是一个控制反转。以及ESFramework回调用户自定义的群组关系和好友关系回调用户自定义的用户管理器以管理在线用户相关状态回调用户自定义的登陆验证处理等等不一而足。再比如与ESFramework一脉相承的轻量级通信引擎StriveEngine通过回调用户自定义的通信协议来实现更加灵活的通信。由此我们也可以总结出开发框架与类库的区别使用开发框架时框架掌握程序流程的控制权而使用类库时则是应用程序掌握程序流程的控制权。或者说使用框架时程序的主循环位于框架中而使用类库时程序的主循环位于应用程序之中。框架会回调应用程序而类库则不会回调应用程序。ESFramework和StriveEngine中最主要的对象都以engine来命名我们也可以看出框架对于程序主循环的控制——它会为你把握方向、眼看前方、轻松驾驭·依赖注入(Dependency Injection)依赖注入与依赖倒置、控制反转的关系仍旧是一本万殊。依赖注入就其广义而言即是通过“注入”的方式来获得依赖。我们知道A对象依赖于B对象等价于A对象内部存在对B对象的“调用”而前提是A对象内部拿到了B对象的引用。B对象的引用的来源无非有以下几种A对象内部创建无论是作为字段还是作为临时变量、构造器注入、属性注入、方法注入。后面三种方式统称为“依赖注入”而第一种方式我也生造了一个名词称为“依赖内生”二者根本的差异即在于我所依赖的对象的创建工作是否由我自己来完成。当然这个是广义的依赖注入的概念而我们一般不会这样来使用。我们通常使用的是依赖注入的狭义的概念。不过直接陈述其定义可能会过于诘屈聱牙我们还是从具体的例子来看。