控制反转≈鸡蛋灌饼?论程序员如何优雅做饼

【控制反转】和【依赖注入】基本上是每个java程序员入门必学的概念

【鸡蛋灌饼】也是每个打工人必(然)吃(过)的口粮

那么它们之间有什么关系呢?

今天想用这篇文章聊聊如何用鸡蛋灌饼更直观地理解控制反转IoC和依赖注入DI的核心思想


1.首先我们先了解什么是控制反转和依赖注入

控制反转(Inversion of Control)

        是一种设计原则,其核心是将对象的创建、生命周期管理及依赖关系的控制从代码中剥离,交由外部容器或框架统一管理。传统开发中,对象通过直接实例化(如new一个)主动获取依赖,而反转则是将控制权交给容器,使对象仅需声明依赖,由容器在运行时动态注入

核心特征:

         控制权转移:从代码内部转移至容器

         解耦与扩展性:组件间依赖关系不再硬编码,而是通过配置或注解动态解析

依赖注入(Dependency Injection)

        是实现控制反转的具体手段,将依赖对象从外部注入到目标组件汇总,而非由组件自行创建。

3种实现方式:

        构造函数注入:通过构造参数传递依赖项

        Setter方法注入:通过公共方法动态设置依赖项

        接口/属性注入:直接通过接口或属性赋值注入


        两者均致力于降低组件耦合度,提高代码可维护性和可测试性,实现灵活扩展

        松耦合:组件仅依赖接口或抽象,而非具体实现

        可维护性:依赖关系集中管理,修改时无需调整业务代码

        可测试性:依赖项可灵活替换为模拟对象,便于单元测试

2.用鸡蛋灌饼来理解控制反转IoC和依赖注入DI的核心思想

1. 传统开发模式(无IoC/DI)

场景‌:
你作为程序员想吃鸡蛋灌饼,需要 ‌自己动手做‌:

  1. 买面粉、鸡蛋、生菜 → ‌自己创建所有依赖对象
  2. 和面、煎饼、调酱料 → ‌内部实现所有细节
  3. 吃完还要洗碗 → ‌管理资源生命周期

代码类比‌:

// 你完全控制所有过程,耦合度高 
public class Programmer { 
    public void eat() {         // 自己造轮子 
        Dough dough = new Dough(); // 自己买面粉和面 
        Egg egg = new Egg(); // 自己买鸡蛋 
        Pancake pancake = dough.fry().add(egg); // 自己煎饼 
        pancake.eat(); //吃
        pancake.clean(); // 自己洗碗 
    } 
}

2. 控制反转(IoC)

场景‌:
你选择 ‌去摊位购买‌,而非自己做

  1. 摊主大妈(‌容器‌)掌控流程:和面、煎饼、加料 → ‌控制权反转到了大妈手里
  2. 你只需说需求:“加两个蛋或者肠,微辣” → ‌定义接口(抽象)
  3. 摊主做好递给你 → ‌返回实例

代码类比‌:

// 你依赖外部容器(摊主)提供服务 
public class Programmer { 
    private PancakeSeller seller; // 依赖抽象(摊位接口) 
    // 依赖注入:通过构造函数传入具体摊主 
    public Programmer(PancakeSeller seller) { 
        this.seller = seller; 
    } 
    public void eat() { // 无需关心实现细节,直接调用接口 
        Pancake pancake = seller.makePancake("双蛋微辣"); // 控制权在摊主 
        pancake.eat(); // 不用洗碗!生命周期由摊主管理 
    } 
}

3. 依赖注入(DI)的3种实现方式
(1) 构造函数注入(最常见)

场景‌:
你在王阿姨摊位前说:“我要一个鸡蛋灌饼,加双蛋” → ‌声明依赖关系
王阿姨(具体实现类)将灌饼做好递给你 → ‌通过构造函数注入依赖

代码类比‌:

PancakeSeller seller = new WangAyiPancake(); // 具体实现:王阿姨摊位 
Programmer you = new Programmer(seller); // 依赖注入 
you.eat();
(2) Setter方法注入

场景‌:
你原本买了原味灌饼,中途说:“等等,加一勺辣酱!” → ‌动态修改依赖

代码类比‌:


public class Programmer { 
    private PancakeSeller seller; // Setter方法注入 

    public void setSeller(PancakeSeller seller) { 
        this.seller = seller; 
    } 
} 
Programmer you = new Programmer(); 
you.setSeller(new SpicyPancakeSeller()); // 动态加了辣酱
you.eat();
(3) 接口注入(较少用)

场景‌:
摊位提供“定制调料”服务,你必须实现一个“加料接口”才能用 → ‌通过接口强制注入

代码世界少用,现实世界也还没实现呢,这世界何尝不是一个巨大的hello world

代码类比‌:


public interface SauceService { 
    void injectSauce(Sauce sauce); 
} 

public class Programmer implements SauceService { 
    private Sauce sauce;

    @Override 
    public void injectSauce(Sauce sauce){ 
        this.sauce = sauce; // 实现接口完成注入 
    } 
} 

Programmer you = new Programmer(); 

you.injectSauce(new ChiliSauce()); // 注入辣酱

4. 为什么IoC/DI更好?
  1. 解耦‌:

    • 你不用关心摊主用哪家面粉、鸡蛋品牌 → ‌依赖抽象,不绑定具体实现
    • 换摊主(如从“王阿姨”换到“李大爷”)只需修改注入对象 → ‌更换实现类不影响主逻辑
  2. 可测试性‌:

    • 单元测试时,用 MockPancakeSeller 模拟摊主 → ‌避免依赖真实摊位
  3. 可维护性:

               做鸡蛋灌饼的流程已经确定了,至于她每天用哪家的鸡蛋或者生菜,影响不大,你能吃出来吗?

  1. 扩展性‌:

    • 新增“芝士灌饼”只需实现 PancakeSeller 接口 → ‌开闭原则(对扩展开放)

总结:买灌饼 vs IoC/DI

动作买灌饼IoC/DI
核心需求吃灌饼获取对象实例
实现者摊主大妈(容器)IoC容器(如Spring)
控制权摊主控制流程容器管理对象生命周期
依赖关系你依赖摊主,但不关心(不控制)Ta怎么实现类依赖接口,而非具体实现类
灵活性换摊主只需走到另一家更换实现类只需修改注入配置

核心思想‌:

  • 程序员(你)‌ 应专注业务逻辑(吃灌饼),而非资源管理(和面、煎饼)。
  • 摊主(容器)‌ 负责依赖创建和调度,实现控制权反转。

注:一时心血来潮,既然面向对象是对现实世界的理解和抽象,那么它们之间肯定是可以相互关联的,或者每一种思想或机制都可以在现实世界找到类似的注解,所以先用最基础的两个概念给这一系列栏目开个篇,名字还没想好,暂时叫【深入浅出之编程概念】吧,哈哈哈^_^

如有不合适地方,欢迎各位程序员指正或者友好讨论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序媛七分

随多随少随你心意^-^

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值