当前位置: 首页 > news >正文

依赖注入与对象间关系

依赖注入(DI)是控制反转(IoC)的一种方式。目前,在.NET和Java领域已经有相当多基于DI思想的对象容器,如:Spring,Unity等。本文试图避免重复性地介绍DI基础知识和DI容器的使用,而是希望深一层探讨DI的本质和对象间关系,以达到合理设计避免滥用DI的目的。

依赖注入 vs 创建对象

有不少地方这样描述:“依赖注入改变了使用对象前先创建的传统方式,而是从外部注入依赖的对象”。这样的描述其实似是而非,先来看一个例子:

interface ICar{
void Run();
}

class Person{
public ICar Car {
set { m_car = value; }
}
public void Drive() { m_car.Run(); }

private ICar m_car;
}

Person不主动创建所依赖的ICar对象,而是通过DI方式注入。应该说这是一种合理的设计,但如果接本段开头的话说“依赖注入改变了人使用汽车之前先创建的传统方式”您会不会觉得别捏呢?我们来看看所谓的“传统方式”是什么样子:

class Benz : ICar{
void Run() {}
}

class Person{
public void Drive() {
ICar car = new Benz();
m_car.Run();
}
}

这就是Drive之前先创建ICar对象,“传统方式”并不传统,因为它非常不自然。我们肯定在偷笑“这个人家里一定是开银行的,要开车的时候临时买一辆奔驰,开完马上扔给垃圾回收站”。这说明DI并非都是反传统的求新求变,在某些情况下它本身就是一种合理的设计。不过,这里话才说了一半,上面的例子是说“不该创建对象的时候,DI本来就是一种合理的设计”,下面还有另一半“该创建对象的时候,用DI反而不合理”。

class Person{
public Person(IHeart heart) { m_heart = heart; }
private IHeart m_heart;
}

大家看看上面这位老兄在干嘛呢?心脏也依赖注入?这就是典型的滥用依赖注入。我们来分析一下这种方式的问题在哪儿:

1. 暴露内部实现:假设m_heart只是内部实现相关的对象,上面的方式就暴露了内部实现,造成外部程序对Person的内部实现的变化变得敏感;

2. 依赖对象状态被外部修改:由于m_heart是从外部注入的,外部可能依然持有m_heart的引用,因此完全可能被有意无意地修改掉。

本质上,这种滥用DI的问题其实是对OO封装的破坏。如果采用创建对象的方式,这个例子就合理多了:

class Heart : IHeart { ... }

class Person{
public Person() { m_heart = new Heart(); }
private IHeart m_heart;
}

Person在其构造函数内自行创建m_heart显然是合理的设计,不存在上面强行引入DI造成的破坏封装问题。

对象间依赖关系

上面的两个例子从不同方面说明了DI和对象创建谁也不能取代谁,应该根据情况采用合理的设计。那么我们自然要问,合理的标准在哪里呢,有没有明确的指导方针供我们设计时参考呢?答案是有!那就是对象间关系。在OO中,对象间关系的大致可分为两类:纵向关系和横向关系。纵向主要指继承关系,比较容易区分;但横向关系比较微妙。从本文的例子中我们已经明显感觉到了横向关系是需要仔细区分的。

按UML建议横向关系大体分为4种,它们的耦合程度由弱到强:

依赖 < 关联 < 聚合 < 组合

1. 依赖(Dependency):语义“a uses b”,a依赖于b,但不持有b的引用;比如:现实世界的例子有“人对空气的依赖关系”,在程序中a.f(b)可以理解为a对b的依赖(或者说对b的类型B的依赖),b作为a的方法参数,a内部成员变量不引用b。UML符号:

​编辑

2. 关联(Association):语义“a has b”,a拥有b的引用,但a和b无从属关系,二者是一种松散的关联关系,可以随时解除或建立;比如本文中“人与汽车关系”。UML符号:

​编辑

3. 聚合(Aggregation):语义“a owns b”,a拥有b的引用,且有从属关系,二者的耦合比关联更强,但a并不负责b的生命周期。在程序中,b并非a专有的内部实现细节;除a以为,外部也可能引用b。比如,”汽车和轮胎关系”,汽车不负责轮胎的生产,汽车报废了轮胎或许还可以继续使用。UML符号:

​编辑

4. 组合(Composition):语义“b is a part of a”,a不仅拥有b的引用,还应该全权负责b的整个生命周期,在程序中b通常是a的内部实现细节,不暴露给外部;比如本文中人与心脏的关系。UML符号:

​编辑

我们注意这四种关系中有一个关键的概念“对象生命周期”,在建模时辨别清楚对象生命周期就不难选择采用DI还是创建对象。DI意味着使用者不负责依赖对象的生命周期,创建对象则相反。对应到上面的4种横向关系,我们一般可以这样处理:第1种依赖关系不需要DI也不需要创建对象;第2,3种关联和聚合关系适合采用DI方式,最后一种组合关系适合采用创建对象方式。

http://www.jsqmd.com/news/1132476/

相关文章:

  • MLS点云道路标线自动化提取:基于PCL与OpenCV实现95%+准确率(附代码)
  • 【学习记录】Week14(二):沙箱机制深度剖析与进阶 ORW 绕过体系
  • 从零学习Kafka:调优
  • # TDengine TMQ 最佳实践 — 可靠消费、容错与监控
  • Node.js-Phase 1 学习总结:CLI 文件管理系统
  • 3分钟极速指南:用Python工具一键获取国家中小学智慧教育平台电子课本
  • 一个独立开发者的审计日志平台
  • Linux 系统中定位与设置 JAVA_HOME 目录
  • MNIST 数据集 3 种主流框架加载对比:PyTorch vs TensorFlow vs Hugging Face Datasets
  • Educator头歌答案分享:数据预处理与特征工程在线实验闯关
  • Pot Desktop:5大核心功能解密,3分钟掌握跨平台翻译神器
  • Java面试突击指南!1个月拿下Java高级开发岗!AI大模型简历必备/面试必备!
  • Agentic RAG 方案深度解析:从概念到落地
  • 如何在Linux上流畅运行Windows游戏:DXVK终极配置指南
  • Fastboot Enhance:Windows平台一站式Android刷机工具箱,告别命令行复杂操作
  • Markdown锚点跳转失败的解决办法
  • Jetson Nano Super + Ubuntu 22.04 + ROS2 Humble:MID-360、FAST-LIO 与 EGO-Planner 实时建图部署记录
  • pytest-xdist分布式测试:加速APP自动化测试的架构与实战
  • 互联网大厂 Java 面试实录:谢飞机的三轮攻防战
  • 前后端分离爱心商城系统系统|SpringBoot+Vue+MyBatis+MySQL完整源码+部署教程
  • 从零搭建一个单节点 K8S 可观测实验室(一):从 Ubuntu 24.04 开始
  • HarmonyOS 小游戏《对战五子棋》开发第3篇-项目配置文件全解析
  • AI 已经改变工作方式,我们该怎么适应这场变化?
  • Codex 使用 Playwright Test Agents
  • 换种不同的方式调用WCF服务[提供源代码下载]
  • 3步解锁中医AI:如何让“仲景“大语言模型成为你的智能中医助手
  • 程序员的语言“艳遇史”(四) ——数学系师姐forth
  • 尽量减少对DOM的操作
  • mnist-数据集的学习-3.6手写数字识别
  • Tableau新手实战指南:3天做出可交付业务看板