编程实战经验系列之极限思想(一)计算机软件中的依赖关系-一切皆依赖
大家好,我是sully。
对于我们编程人员来说,实战经验无疑是最重要的。可是网上的课程、教程不是教学语法基础和算法基础,就是教学实战项目。如果只是单纯的堆项目,那我们只能是一个编码机器,水平只能停留在初级。项目是什么样,我就怎么写,规范怎么定,我就怎么做。老师教我们怎么写代码,却没教我们怎么写好代码;教我们如何编译代码,却没教我们如何构建应用程序;教我们软件工程,却没教我们如何团队协作;教我们数据结构与算法,却没教我们如何实战应用;教我们实战项目就是这样做,却没教我们实战项目为什么要这样做。
真正能让程序员蜕变为中级工程师甚至高级工程师的,无疑是从无数的实战经验中提炼出来的核心思想和思维方式。
为此,我计划编写一个编程实战经验系列,分享我从业十多年来的实战经验和思想。如果我的文章能帮助到您,那算是我为技术做的一点点贡献,如果没有,那就当是消遣。
所有文章拒绝AI代写,可放心食用!
计算机软件中的依赖关系-一切皆依赖
极限思想:一切皆依赖。从依赖的眼光审视软件世界。
软件开发中的依赖关系是构建现代软件系统的基石,也是软件工程的核心概念之一。在当今高度模块化和组件化的软件开发环境中,几乎所有的软件都建立在各种依赖关系之上,毫不夸张地说,所有的软件都是用依赖堆起来的。从应用软件的运行时依赖,到包管理工具中的依赖声明,到面向对象编程中的类间依赖,再到系统架构中的组件间依赖,这些依赖关系共同构成了软件的”血脉系统”。然而,不当的依赖管理会导致软件质量下降、维护困难、安全风险增加等问题。理解软件依赖关系的本质、类型和管理方法,是软件工程师必备的专业素养,也是构建高质量、可维护、安全软件系统的关键。理解并掌握软件的依赖关系,你就能从初级程序员变成中级工程师。
一、软件依赖关系的基本概念
软件依赖关系是指一个软件实体(如类、模块、组件、库或服务)需要另一个软件实体才能正常运行或完成特定功能的关系。这种关系可以是显式声明的,也可以是隐式存在的。在软件开发中,依赖关系无处不在,可以说,一切皆依赖。依赖关系构成了软件系统的结构基础,同时也可能成为系统脆弱性的来源。
直接依赖是指软件实体A显式地声明需要软件实体B才能正常运行。例如,在Java项目中,pom.xml文件中声明的依赖项;在Python项目中,requirements.txt中列出的库;在C#项目中,Program.cs中注册的服务等。直接依赖是开发人员可以明确控制的依赖关系,通常在代码或配置文件中直接体现。
间接依赖(也称为传递依赖)是指软件实体A依赖于软件实体B,而B又依赖于C,此时A对C的依赖就是间接依赖。间接依赖通常由开发人员无法直接控制的依赖路径引入。例如,如果项目依赖了库X,而X又依赖了库Y,那么项目就会间接依赖Y。这种传递性使得软件系统中的依赖关系变得极其复杂,难以完全掌握。
依赖关系的强度可以分为硬依赖和软依赖。
硬依赖是软件运行所必需的,缺失会导致系统崩溃或功能不可用;软依赖则是可选的,缺失仅会导致部分功能不可用,但核心功能仍然正常运行。例如,sktime库中的PyTorch依赖属于软依赖,只有在使用深度学习功能时才需要 。
软件依赖关系还具有方向性和可传递性。方向性指的是依赖关系通常是单向的,如A依赖B,但B不一定依赖A;可传递性则指如果A依赖B,B依赖C,那么A通常也会依赖C,除非存在特定的依赖隔离机制。
二、软件依赖关系的类型分类
软件依赖关系可以根据不同的维度和层面进行分类,了解这些分类有助于更好地理解和管理依赖。
1. 按依赖层级分类(重点)
包/库级依赖:指软件项目与外部库或包之间的依赖关系。这类依赖通常由包管理器(如Maven、npm、NuGet等)管理,是软件开发中最基础的依赖类型。例如,Java项目依赖Spring框架,Python项目依赖NumPy库等。
面向对象级依赖:指类与类之间的依赖关系,包括依赖、关联、聚合、组合、实现和继承等。这类依赖关系在代码层面直接体现,是软件设计和架构的重要组成部分 [1] 。例如,Cat类依赖于Toy类,Dog类拥有Hat类的关联关系等。
组件/服务级依赖:指系统中不同组件或服务之间的依赖关系。在分布式系统或微服务架构中,这类依赖关系尤为重要,涉及服务发现、负载均衡、容错处理等问题 。
2. 按依赖性质分类
强依赖(硬依赖):软件必须满足的依赖,否则系统无法运行或核心功能不可用 。例如,一个Java项目依赖JDK,或者一个微服务依赖数据库服务。
弱依赖(软依赖):软件部分功能需要的依赖,缺失时仅影响特定功能,但核心功能仍可运行 。例如,一个Web应用依赖于某个第三方分析库,但缺失时仅影响用户行为分析功能。
3. 按依赖范围分类
编译时依赖:在编译过程中需要的依赖,缺失会导致编译失败 。
运行时依赖:在程序运行时需要的依赖,缺失会导致运行时错误 。
测试依赖:测试时所需要的依赖,用于完成测试所需的模拟、替代等功能。
三、依赖关系管理的方法与工具
1. 依赖声明与版本控制
依赖声明是管理软件依赖关系的基础,不同语言和平台有不同的依赖声明方式(这里可以对比讲解不同类型项目的运行安装方式的共同点):
|
语言/平台 |
依赖声明文件 |
版本范围语法 |
依赖调解机制 |
|
Java (Maven) |
pom.xml |
[1.0,2.0), 1.0, 1.0+ |
最短路径优先,声明顺序优先 |
|
JavaScript (npm) |
package.json |
^1.2.3, ~1.2.3, >=1.0 |
最近定义优先,依赖路径优先 |
|
Python (pip) |
requirements.txt |
==1.0, >=1.2, <2.0 |
最新版本优先 |
|
Python (Poetry) |
pyproject.toml |
^1.2.3, ~1.2.3 |
最新版本优先,但更精确 |
|
.NET (NuGet) |
.csproj |
[1.0,2.0), 1.0, 1.0+ |
最低适用版本优先 |
版本控制是依赖管理的重要组成部分,遵循语义化版本(SemVer)规则: - 主版本号:不兼容的API更改时递增 - 次版本号:向下兼容的功能添加时递增 - 修订号:向下兼容的问题修复时递增
不同版本范围语法的含义: - ^1.2.3:允许1.x.x的最新版本,但不升级到2.0.0 - ~1.2.3:允许1.2.x的最新版本,但不升级到1.3.0 - [1.0,2.0):允许1.0到2.0之间的任何版本,但不包括2.0 [3]
我们拿到一个新项目,首先都是无脑地install,安装或下载好项目所需的依赖。
问题:为什么要使用工具来管理依赖,而不是手动复制包?
在软件开发中,使用工具管理依赖项(如 npm、pip、Maven 等)可以自动处理版本控制、依赖解析和传递性依赖,避免手动复制包带来的版本冲突、重复冗余和维护困难等问题;
同时还能确保团队成员和部署环境使用一致的依赖配置,提升项目的可复现性、可维护性和构建效率。
此外,在团队开发中如果不使用依赖管理工具,如果将依赖项提交到Git,则会对git带来非常大的压力,如果不提交,则每个成员都要手动维护依赖项并保证每个人之间的依赖项一致,这也是一个不小的成本。
2. 循环依赖检测与解决
循环依赖(Cyclic Dependency)是软件工程中的常见问题,指两个或多个软件实体互相依赖,形成环路 。例如,模块A依赖模块B,模块B又依赖模块A,这就形成了循环依赖。
循环依赖的危害: - 编译失败:在编译时,编译器无法确定依赖顺序。 - 维护困难:模块间高度耦合,难以单独修改或替换。 - 测试复杂:单元测试难以隔离循环依赖的模块。
循环依赖检测工具: - npm ls:显示依赖树并标记循环依赖。 其余略。
循环依赖解决方案:
-
- 接口隔离:定义独立接口,模块间仅依赖接口而非具体实现 。
- 重构代码:打破循环依赖,如通过引入第三方模块或调整类的职责 。
- 弱引用/无主引用:在内存管理中,使用弱引用(Weak Reference)或无主引用(Unreachable Reference)避免对象循环引用导致的内存泄漏 [2] 。
问题:依赖会引入整个组件,就算只使用组件中的一个很小的功能。这带给我们什么启发?
-
- 第一、最小化依赖原则,引入依赖项时评估其必要性,能自己实现的小功能,若逻辑简单且无安全或兼容性风险,避免引入外部依赖。
- 第二、最小化依赖原则,引入依赖项时评估依赖组成,优先选用设计良好、支持细粒度导入的库。
- 第三、在设计库时应考虑单一职责原则。
问题:为什么有些组件会强调自己是零依赖,即零依赖的优势和好处?(这里对比一下SqlSugar和FreeSQL,明明SqlSugar更老牌,我为什么不选它?)
在软件开发中,某些组件或库会特别强调自己是“零依赖”(zero-dependency),这通常是为了突出其轻量、自包含和高可移植性的特点。
所谓“零依赖”,指的是该组件在运行或使用时不需要额外安装或引入第三方库或外部模块,所有功能都由自身代码实现。这种设计具有多方面的优势:
首先,它极大地简化了项目的依赖管理,避免了因引入外部依赖而可能引发的版本冲突、兼容性问题或“依赖地狱”(dependency hell);
其次,零依赖组件通常体积更小、启动更快、资源占用更低,特别适合嵌入式系统、微服务、命令行工具或对性能和启动时间敏感的场景;
第三,由于不依赖外部库,这类组件的可移植性和部署便捷性显著提高,用户只需拷贝或引入单一文件即可使用,无需担心目标环境中是否已安装特定依赖;
此外,从安全角度看,零依赖也减少了潜在的攻击面,因为每个第三方依赖都可能引入未知漏洞或恶意代码,而自包含的实现更容易进行审计和验证;
最后,在开源生态中,零依赖组件往往更受开发者欢迎,因为它们降低了使用者的学习成本和集成门槛,提升了整体开发体验。
因此,“零依赖”不仅是一种技术选择,更是一种对简洁性、可靠性和用户体验的承诺。


四、控制反转与依赖注入
1. 控制反转(IoC)原理
控制反转(Inversion of Control, IoC)是一种设计模式,其核心思想是将对象的创建和管理从代码中”反转”到外部容器 。在传统软件开发中,对象A直接创建并管理对象B;而在IoC模式中,对象A和对象B都由外部容器管理,容器负责将对象B注入到对象A中。
IoC的实现方式主要有两种: - 依赖注入(Dependency Injection, DI):容器将依赖对象注入到组件中 。 - 服务定位器(Service Locator):组件通过服务定位器获取依赖对象。
依赖注入是IoC最常用的实现方式,它通过以下三种主要形式实现:
-
- 构造函数注入:适用于强依赖。
- 设值注入(Setter Injection):适用于弱依赖。
- 接口注入:本质上还是设值注入,只不过使用了接口规定了注入方法。
2. 依赖注入容器
依赖注入容器是实现IoC的核心工具,负责管理对象的创建、配置和依赖关系 。主流的依赖注入容器包括:
|
容器 |
适用平台 |
特点 |
注入方式 |
|
Spring |
Java |
轻量级、功能丰富、集成AOP |
构造函数注入、设值注入、注解注入 |
|
.NET Core DI |
.NET |
内置于框架、简单易用 |
构造函数注入、属性注入、方法注入 |
|
Autofac |
.NET |
模块化注册、高级生命周期控制 |
构造函数注入、设值注入、属性注入 |
|
Unity |
.NET |
微软开发、支持基本DI功能 |
构造函数注入、设值注入、方法注入 |
|
SimpleInjector |
.NET |
性能优越、支持装饰器 |
构造函数注入、属性注入 |
依赖注入容器的工作原理:
-
- 对象注册:在容器中注册对象及其依赖关系。
- 依赖解析:容器根据注册信息解析依赖关系,创建对象实例。
- 对象注入:容器将创建好的对象实例注入到需要的地方 。
- 生命周期管理:容器管理对象的生命周期,如单例、作用域、瞬态等。
3. 控制反转与依赖倒置原则
控制反转(IoC)和依赖倒置原则(Dependency Inversion Principle, DIP)密切相关但又有区别。
IoC关注的是对象的创建和管理,将这些责任从代码中转移到外部容器;而DIP关注的是模块间的依赖关系,强调高层模块不应该依赖低层模块,两者都应该依赖抽象 。
DIP的数学表达可以表示为: - 高层模块 → 接口 ← 低层模块
这意味着高层模块和低层模块都依赖于接口,而不是彼此直接依赖。这种设计使得模块间耦合度降低,系统更易于维护和扩展。
依赖倒置原则的实现步骤: 1. 识别高层模块和低层模块。 2. 定义接口或抽象类,作为两者之间的依赖点。 3. 高层模块依赖接口,而非具体实现。 4. 低层模块实现接口,而非直接与高层模块交互。
问题:我们应该从面向对象编程转变为面向接口编程,面向接口编程的核心就是依赖倒置。为什么要依赖接口,而不是依赖具体的实现?
在软件工程的演进过程中,从单纯依赖具体实现的面向对象编程(OOP)转向以“面向接口编程”为核心的设计范式,本质上是为了应对复杂系统中日益增长的可维护性、可扩展性和可测试性需求。
其核心理念正是“依赖倒置原则”(Dependency Inversion Principle, DIP):高层模块不应依赖于低层模块,二者都应依赖于抽象;抽象不应依赖于细节,细节应依赖于抽象。
之所以要依赖接口(即抽象)而非具体的实现,是因为具体实现往往是易变的、脆弱的,且与特定业务逻辑或技术细节紧密耦合;一旦实现发生变化(如更换数据库、调整算法、切换第三方服务),所有直接依赖它的代码都可能需要随之修改,从而引发连锁反应,破坏系统的稳定性。
而接口作为行为契约,定义了“能做什么”,却隐藏了“如何做”的细节,使得调用方只需关心功能契约,无需了解底层机制。
这种解耦不仅让系统各部分可以独立演化——例如在不改动业务逻辑的前提下替换日志记录方式或支付网关——还极大提升了代码的可测试性,因为我们可以轻松地用模拟对象(Mock)或桩(Stub)实现接口来隔离依赖进行单元测试。
此外,依赖接口也促进了多态性和插件化架构的实现,使系统更具弹性与适应性。
因此,面向接口编程并非否定面向对象,而是对其更高层次的升华:它通过抽象隔离变化,将软件从“紧耦合的实现网络”转变为“松耦合的契约协作”,从而构建出更健壮、灵活和可持续演进的软件系统。
4. 微服务中的IoC应用
在微服务架构中,IoC和DI同样重要,但应用方式有所不同。微服务中的IoC主要体现在:
-
- 服务间依赖解耦:通过消息队列中间件解耦微服务之间的强依赖,微服务只与消息队列通信。例如,订单微服务创建订单,通过消息队列中间件通知库存微服务扣减库存。
- 服务注册与发现:使用服务注册中心(如Consul、Eureka)管理服务实例,实现动态依赖注入。
- 容器化部署:Docker和Kubernetes等容器技术提供依赖隔离机制,确保服务独立部署和运行。
微服务IoC的优势:
-
- 松耦合:服务间通过接口交互,降低耦合度 。
- 可扩展性:可以独立扩展和部署服务 。
- 可维护性:修改一个服务不会影响其他服务 。
- 可测试性:单元测试中可以模拟依赖服务。
五、依赖关系管理与设计模式
问题:设计模式中体现了哪些处理依赖关系的原则?换句话说,掌握了处理依赖关系的哪些原则,就相当于掌握了设计模式?
设计模式本质上是面向对象系统中反复出现的问题的可复用解决方案,而其中绝大多数模式的核心目标之一,正是合理地管理、解耦和控制组件之间的依赖关系。
因此,掌握处理依赖关系的关键原则,几乎就等于掌握了设计模式的精髓。
首先,依赖倒置原则(Dependency Inversion Principle) 是最根本的指导思想:高层模块不应依赖低层模块,二者都应依赖抽象;抽象不应依赖细节,细节应依赖抽象。这一原则直接体现在工厂模式、策略模式、模板方法、依赖注入等众多模式中,通过引入接口或抽象类,将具体实现与使用逻辑分离。
其次,依赖最小化原则(也称“最少知识原则”或迪米特法则) 强调一个对象应当尽可能少地了解其他对象的内部结构,只与“朋友”通信,从而降低耦合度——这在中介者模式、外观模式中尤为明显。
第三,组合优于继承(Composition over Inheritance) 的思想贯穿于装饰器模式、桥接模式、策略模式等,通过将行为封装为可替换的组件并以组合方式注入,而非通过继承硬编码依赖,使得系统更灵活、更易扩展。
为什么组合优于继承?不需要理解太多的理论,只需要理解:继承是子类继承父类,本质上是子类依赖父类,是类与类之间的依赖,是强依赖,违反了依赖倒置原则;而组合是通过组合不同组件的接口,是对接口的依赖。
此外,控制反转(Inversion of Control) 作为依赖管理的宏观机制,将依赖的创建与使用分离,由外部容器或框架负责装配,这正是依赖注入模式的基础,也是现代框架(如 Spring)的核心理念。
最后,开闭原则(对扩展开放,对修改关闭) 要求系统在不修改现有代码的前提下支持新功能,而这只有在依赖抽象、松耦合的前提下才能实现。综上所述,设计模式并非孤立的技巧集合,而是围绕“如何优雅地处理依赖”这一核心问题所形成的一套系统性思维:通过抽象化、延迟绑定、委托、组合与隔离等手段,将紧耦合的硬依赖转化为松耦合的软依赖,从而构建出高内聚、低耦合、易测试、易维护且可演进的软件架构。因此,真正理解并熟练运用这些依赖管理原则,就等于掌握了设计模式的灵魂。
六、三种依赖关系对比与启发
问题:软件中的依赖关系按层级可以分为三种,分别是包或库级依赖、面向对象依赖和组件与服务间的依赖。我想从它们的对比中获取到一些启示,请从依赖关系管理的方法、多个原则和方面来对比它们,最好以表格方式呈现。
| 对比维度 | 包/库级依赖(Package/Library-level Dependency) | 面向对象依赖(Object-Oriented Dependency) | 组件与服务间依赖(Component/Service-level Dependency) |
|---|---|---|---|
| 定义范围 | 模块或项目对第三方库、框架或内部模块的引用(如 Maven 依赖、npm 包) | 类与类、接口与实现之间的调用或继承关系 | 系统内独立部署单元(微服务、插件、模块)之间的交互 |
| 粒度 | 粗粒度(整个库或模块) | 细粒度(单个类、方法、接口) | 中到粗粒度(服务、组件、API 接口) |
| 依赖管理方法 | - 依赖声明(如 package.json, pom.xml) - 依赖解析与版本锁定 - 构建工具(Maven, Gradle, npm) |
- 依赖注入(DI) - 接口抽象 - 工厂/策略模式 - 控制反转(IoC) |
- API 网关/服务注册发现 - 契约测试(如 Pact) - 服务编排/网格(如 Istio) - 异步消息队队(Kafka, RabbitMQ) |
| 核心设计原则 | - 最小依赖原则 - 语义化版本控制(SemVer) - 单一职责(库本身) |
- 依赖倒置原则(DIP) - 开闭原则(OCP) - 接口隔离原则(ISP) - 里氏替换原则(LSP) |
- 松耦合 - 明确契约(API/事件) - 容错与弹性(断路器、重试) - 服务自治 |
| 变更影响范围 | 影响整个构建产物;版本升级可能引入破坏性变更 | 局部影响,但若违反封装可能导致连锁修改 | 跨团队/系统影响;需协调发布与兼容性(向后兼容) |
| 测试策略 | - 单元测试 + 集成测试 - 依赖模拟(Mocking)有限 |
- 单元测试为主 - Mock/Fake 对象广泛使用 |
- 契约测试 - 端到端测试 - 混沌工程 |
| 典型工具/技术 | Maven, Gradle, npm, pip, Cargo, NuGet | Spring DI, Dagger, Guice, 手动工厂类 | gRPC, REST, OpenAPI, Service Mesh, Docker/K8s, Kafka |
| 常见问题 | - 依赖冲突(diamond dependency) - 安全漏洞(如 Log4j) - 版本漂移 |
- 紧耦合导致难以测试/复用 - 过度继承 - 循环依赖 |
- 网络延迟/故障 - 数据一致性(分布式事务) - 服务雪崩 |
| 治理重点 | 依赖审计、SBOM(软件物料清单)、许可证合规 | 代码结构清晰、高内聚低耦合、可测试性 | SLA/SLO、可观测性(日志/指标/追踪)、版本兼容策略 |
| 演进方向启示 | → 使用模块化构建系统,限制传递依赖,自动化安全扫描 | → 优先依赖抽象而非具体实现,强化接口设计 | → 采用事件驱动或异步通信降低同步耦合,推行契约先行 |
七、广义的依赖项的理解
问题:在软件开发中,狭义的依赖项即项目依赖的库、包和组件;而对于项目整个生命周期所需要的的所有外部的东西,例如:执行硬件、执行平台、执行环境、项目中使用的可执行文件、网络通信调用的上游应用和服务、项目中使用的其他所有文件甚至于法律条文,都可以称为项目的依赖项,这是广义上的依赖。关于广义依赖性的理解,能带给我们什么启发?
对广义依赖性的理解,能够深刻拓展我们对软件系统复杂性与脆弱性的认知边界。
在传统开发实践中,开发者往往只关注代码层面的库、框架或包管理器所声明的依赖项,这种狭义视角虽能解决编译和运行时的基本问题,却容易忽略系统在真实世界中运行所依赖的更广泛生态。
而一旦将视野扩展到广义依赖——包括操作系统版本、CPU架构、容器运行时、云平台特性、网络拓扑、上游微服务接口、外部API的稳定性、配置文件、许可证合规要求、甚至地方法律法规(如GDPR、CCPA等数据隐私法)——我们便能意识到:软件并非孤立存在,而是嵌入在一个由技术、组织、法律和社会因素交织而成的动态网络之中。
这种认知促使我们在架构设计阶段就考虑“可移植性”“可观测性”“容错性”与“合规性”,在部署策略上采用基础设施即代码(IaC)和不可变部署以固化环境依赖,在运维层面通过服务网格或契约测试保障对外部服务变更的鲁棒性,并在项目治理中引入SBOM(软件物料清单)和许可证扫描工具以应对法律风险。
更重要的是,广义依赖思维推动团队从“功能交付”转向“系统韧性建设”,强调跨职能协作——开发、运维、安全、法务乃至业务方需共同识别、评估和管理这些隐性依赖,从而构建出真正可持续、可演进且负责任的软件系统。
因此,广义依赖性不仅是一种技术概念,更是一种系统性思维范式,它提醒我们:软件的成败,往往不取决于代码本身是否优雅,而在于我们是否充分理解并妥善应对了它所栖身的整个依赖宇宙。
八、依赖关系管理的未来趋势(作为了解)
1. 自动化依赖管理
自动化依赖管理是未来的重要趋势,包括:
-
- 依赖关系可视化:通过交互式图表展示依赖关系,帮助理解复杂系统。
2. 依赖关系的度量与分析
依赖关系的度量与分析将更加精细化和量化:
-
- 依赖复杂度度量:如依赖深度、依赖宽度、循环依赖数量等。
- 依赖稳定性度量:如依赖变更频率、依赖项维护状态等。
- 依赖影响分析:分析依赖项变更对系统的影响范围和程度。
九、写在最后
1. 如何通俗的理解依赖?
我们可以使用字面意思来理解依赖,软件使用了的所有组件、文件、依据,则软件对它们构成依赖,软件依赖它们而得以产生、存活、运行,如果它们某一个出问题,则软件不能正常运行或立足。就像做一个证明题一样,证明的结果是依赖于它所使用的定理的,如果某个定理被爆出有问题,那么证明的结果将会错误。就像这篇文章一样,如果文章的参考文献出问题,那么本篇文章一定不是完全正确的(就算没有引用参考文献中的谬误部分)。所以在软件开发中,我们依赖抽象而不是依赖实现,是因为实现的变动性和不稳定性非常大,是因为抽象不容易更改。
2. 如何理解一切皆依赖?
计算机软件中的调用关系、引用关系、运行的硬件、运行的平台、软件之所以开发的法律条文我们都可以称为依赖关系,简而言之就是,软件得以产生、生存和运行的一切依据都是依赖,软件开发中的方法调用关系、类与模块的的引用关系、类的继承关系、接口与实现类关系我们都可以称为依赖关系。在对广义依赖项的理解的基础上,将依赖思想扩大到整个世界,人与人之间有依赖关系,人与物,物与物之间也有依赖关系。整个世界就是所有依赖关系的总和。奇怪的知识又增加了。
参观一下巨婴是如何对项目进行组织的:




十、参考文献
参考来源:
浙公网安备 33010602011771号