简单谈谈控制反转 ( Inversion of Control )

在上一篇《关于原则、模式、框架的概念梳理》中,我提到最近在学习一些基础理论,例如一些简单的编程原则,设计模式等。那么本文就来谈谈我对于控制反转这个概念的理解。因为纯粹是个人学习的总结与理解,如有不足之处,欢迎拍砖,加以指正。

本文将涉及到的概念

  • 单一职责原则
  • 控制反转原则
  • 依赖倒置原则
  • 静态工厂模式
  • 抽象工厂模式
  • 依赖注入模式(方法注入)

思考了一下,总觉得直接一上来就用下定义的方式来说明上面的概念会显得皱皱巴巴,麻麻赖赖,一点都不圆润,所以我想通过最近 与房子打交道的经历 来把上述的概念一下,不知效果会如何...🙄

对于装修的思考

其实我买的是精装房 😆

但这并不影响我对装修的思考,我甚至在买房之前就思考了一些和装修相关的事情,那么接下来我将用代码告诉你,在买房之前我都想了些什么...

假如我买了毛坯房

那么这意味着我将得自己装修,也就是需要去找各种装修工人,嗯...我的自述大概是这样:

一位 *苦逼* 毛坯房业主的装修独白

那么对应的代码应该就是这样的:

    /// <summary>
    /// Tightly coupled class
    /// </summary>
    public class RoughcastHost
    {
        public RoughcastHost()
        {
            Console.WriteLine("I'm the host of roughcast house...");
        }

        private void BuyARoughcastHouse()
        {
            Console.WriteLine("I bought a roughcast house...");
        }

        private void Decorate()
        {
            Console.WriteLine("I want to decorate my house...");
            
            var hardwareWorker = FindHardwareWorker();
            hardwareWorker.Decorate();
            
            var floorFinisher = FindFloorFinisher();
            floorFinisher.Decorate();

            var kitchenAndBathWorker = FindKitchenAndBathWorker();
            kitchenAndBathWorker.Decorate();

            var paperhanger = FindPaperhanger();
            paperhanger.Decorate();
            
            var painter = FindPainter();
            painter.Decorate();

            Console.WriteLine("Decorate house done...");
        }

        public void ToLiveInNewHouse()
        {
            BuyARoughcastHouse();
            Decorate();
            
            Console.WriteLine("Now I can live in new house...");
        }

        private HardwareWorker FindHardwareWorker()
        {
            Console.WriteLine("Find HardwareWorker...");
            return new HardwareWorker();
        }

        private KitchenAndBathWorker FindKitchenAndBathWorker()
        {
            Console.WriteLine("Find KitchenAndBathWorker...");
            return new KitchenAndBathWorker();
        }

        private Painter FindPainter()
        {
            Console.WriteLine("Find Painter...");
            return new Painter();
        }

        private Paperhanger FindPaperhanger()
        {
            Console.WriteLine("Find Paperhanger...");
            return new Paperhanger();
        }

        private FloorFinisher FindFloorFinisher()
        {
            Console.WriteLine("Find FloorFinisher...");
            return new FloorFinisher();
        }
    }

其实从自述就已经可以发现问题了,我作为一位“苦逼”业主,管的事情太多了,可以尝试数数看我都干了什么事情:

  1. 买房
  2. 装修
    1. 找五金件工人,完成封阳台的工作
    2. 找地板匠,完成铺地板工作
    3. 找厨卫工人,监督完成厨卫装修
    4. 找墙纸工人,全屋贴壁纸
    5. 找到油漆匠,室内门、踢脚线上漆
  3. 住进新家

🙄是不是很“苦逼” ?不光要 装修房子所依赖的各类工人,还得 监控 整个装修流程。这与我作为一个业主的主要职责不符啊,我觉得的主要职责就是 拎包入住,那么其他任何事情我都应该能少管就少管

为什么我会有这样的想法呢?因为我想到了这样一个原则

单一职责原则:(Single responsibility principle)规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。所有它的(这个类的)服务都应该严密的和该功能平行(功能平行,意味着没有依赖)。

基于这一点,理想状态下,只需要关注 拎包入住 这一个职责。但是为了这个职责,不得不以 买房装修 为前提,而 买房 是需要亲力亲为的事情,那么看上去,也就 装修 这部分可以尝试从目前的众多职责中移出去。

想到这里,我认为只要有办法让我少操心,买个毛坯房也还不错。

那么该怎么少操心呢?🤔

假如我认识一个包工头

嗯,起初我觉得最费事的就是 找工人 ,所以我觉得如果认识个 包工头,我只需要告诉他我现在需要什么工人,那么包工头就会帮我找到,但为了装修质量,我觉得自己还是得监控工作流程,如此一来,我的自述就变了:

“有了些经验”的业主的独白

那么代码就是这个样子:

    /// <summary>
    /// Try to use IoC principle to move dependency creating out of this class.
    /// </summary>
    public class ExperiencedRoughcastHost
    {
        public ExperiencedRoughcastHost()
        {
            Console.WriteLine("I'm the experienced host of roughcast house...");
        }

        private void BuyARoughcastHouse()
        {
            Console.WriteLine("I bought a roughcast house...");
        }

        private void Decorate()
        {
            Console.WriteLine("I want to decorate my house...");
            Console.WriteLine("I call the labor contractor...");
            
            var hardwareWorker = LaborContractor.RecommendWorker("Hardware");
            hardwareWorker.Decorate();
            
            var floorFinisher = LaborContractor.RecommendWorker("Floor");
            floorFinisher.Decorate();

            var kitchenAndBathWorker = LaborContractor.RecommendWorker("KitchenAndBath");
            kitchenAndBathWorker.Decorate();

            var paperhanger = LaborContractor.RecommendWorker("Wall");
            paperhanger.Decorate();
            
            var painter = LaborContractor.RecommendWorker("Paint");
            painter.Decorate();

            Console.WriteLine("Decorate house done...");
        }
        
        public void ToLiveInNewHouse()
        {
            BuyARoughcastHouse();
            Decorate();
            
            Console.WriteLine("Now I can live in new house...");
        }
    }

😆,快看,我这个“苦逼业主”是不是有所成长?我不用到处跑着找工人了,只需要给认识的包工头打个电话,我依赖工人就会联系我。

这样,我就将之前在装修时还要依赖的各种找工人的事情移出去啦!

而被移出的那些我不想关心的事情,就交给了包工头,那么他就应该做这样的事情:

    /// <summary>
    /// Static Factory
    /// </summary>
    public static class LaborContractor
    {
        public static Worker RecommendWorker(string workType)
        {
            Worker worker;
            switch (workType)
            {
                case "Hardware": worker = RecommendHardwareWorker();
                    break;
                case "Floor": worker = RecommendFloorFinisher();
                    break;
                case "KitchenAndBath": worker = RecommendKitchenAndBathWorker();
                    break;
                case "Wall": worker = RecommendPaperhanger();
                    break;
                case "Paint": worker = RecommendPainter();
                    break;
                default:
                    throw new NotSupportedException("No this type worker in our labor market.");
            }

            return worker;
        }

        private static HardwareWorker RecommendHardwareWorker()
        {
            Console.WriteLine("HardwareWorker connect to me...");
            return new HardwareWorker();
        }

        private static FloorFinisher RecommendFloorFinisher()
        {
            Console.WriteLine("FloorFinisher connect to me...");
            return new FloorFinisher();
        }

        private static KitchenAndBathWorker RecommendKitchenAndBathWorker()
        {
            Console.WriteLine("KitchenAndBathWorker connect to me...");
            return new KitchenAndBathWorker();
        }

        private static Paperhanger RecommendPaperhanger()
        {
            Console.WriteLine("Paperhanger connect to me...");
            return new Paperhanger();
        }

        private static Painter RecommendPainter()
        {
            Console.WriteLine("Painter connect to me...");
            return new Painter();
        }
    }

换句话说,也就是将本来应该由所控制的对于所依赖的各种工人的创建权限(职责)移交给包工头,那么这种将自身依赖创建的控制权反转到外部的理念就是另一个原则 --- 控制反转的体现之一.

控制反转原则:(Inversion of control principle)将对象所拥有的对除主要职责外的所有其他职责的控制权交出以降低代码耦合度。

这个原则的落地方式在上述包工头代码中使用了静态工厂模式

再回到我的装修历程,看上去认识了一个包工头,我的工作就减少了很多,但是想了想,我也没那么多时间在施工现场待着啊,那也就意味着我没时间去控制整个施工流程啊,更何况这种细节的掌控也不应该是的主要职责吧。那么是不是也应该移交出去呢?

可是如果这部分权限也交出去,那看上去我就没必要买毛坯房了,如此“懒惰”的我就开始设想,假如我买了精装房会是怎样的呢?

假如我买的是精装房(暂时忽视软装部分)

那么之前让我头疼的装修部分的各种工作应该都不是需要关心的了,在我买了房子并与开发商签订购房合同之后,那些事情就都应该是开发商需要考虑的了,我就只需等着收房、入住😆,那样的话,的自述大概是这样的:

一位精装房业主的自述

对应的代码就应该是这样的:

    /// <summary>
    /// Try to use IoC and DIP to make this class looser coupled with Dependency.
    /// </summary>
    public class HardcoverRoomHost
    {
        private IWorking _hardcoverHouseDeveloper;

        public HardcoverRoomHost()
        {
            Console.WriteLine("I'm the host of hardcover house...");
        }

        private void BuyAHardCoverHouse(IWorking proxy)
        {
            Console.WriteLine("I bought a hardcover house...");
            _hardcoverHouseDeveloper = proxy;
        }

        public void ToLiveInNewHouse()
        {
            BuyAHardCoverHouse(new HardcoverHouseDeveloper());
            _hardcoverHouseDeveloper.Decorate();
            
            Console.WriteLine("Now I can live in new house...");
        }
    }

如前文所说,买房的步骤是必须有的,在买精装房时尤为重要,因为它意味着选定所依赖开发商,而装修完全不用再操心了,因为会有绑定的开放商来全权负责。
相对于之前的毛坯房 + 包工头形式,买个精装房,在装修这个问题上,则连流程控制的权限都移交了,从内部将对流程的控制权限转移到外部,这同样是控制反转原则的体现之一

再来说说开发商,一般而言,开发商都会有自己的外包工程队,那么工程队会有各种工人,并且施工流程也是工队自己制定的(业主又没办法在施工过程中监工🙄,开发商估计也不会在意具体的施工流程)...那么开发商
代码就应该是这样:

    /// <summary>
    /// Abstract Factory
    /// </summary>
    public class HardcoverHouseDeveloper : IWorking
    {
        private readonly List<Worker> _workerTeam = new List<Worker>();

        public HardcoverHouseDeveloper()
        {
            Assembly
                .GetAssembly(typeof(Worker))
                .GetTypes()
                .Where(type => type.IsClass)
                .Where(type => !type.IsAbstract)
                .Where(type => type.IsSubclassOf(typeof(Worker)))
                .ToList()
                .ForEach(workerType => _workerTeam.Add((Worker)Activator.CreateInstance(workerType)));
        }

        public void Decorate()
        {
            Console.WriteLine("Property developer start Decorating...");
            Parallel.ForEach(_workerTeam, worker => worker.Decorate());
            Console.WriteLine("Decorate house done...");
        }

而在实现开发商时,这次使用了抽象工厂模式。也就是说,对于同样的控制反转原则,可以使用不同的“模式”使之落地

那么再回到买房的考虑中来,从“懒惰”的特性出发,只想关心拎包入住的主要职责,最终我选择了购买精装房

谢谢你看到这里

之前提过,本文会涉及一些基本概念,如果能从上文中发现到所有概念的对应代码或故事中的映射,那说明你我对于那个概念的理解基本相同,如果没有发现,也没关系,👇就是我的一些总结:

“原则给出最佳实践的方针,但并没有细节”
“模式是原则对应的实践方式的细节”

三个原则(Principles):

单一职责原则:(Single responsibility principle)规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。所有它的(这个类的)服务都应该严密的和该功能平行(功能平行,意味着没有依赖)。

作用: 引导开发人员封装方法、类,设置作用域。


控制反转原则:(Inversion of control principle)将对象所拥有的对除主要职责外的所有其他职责的控制权交出以降低代码耦合度。

将控制权交到哪里,取决于采用的设计模式。

其他职责包括:

  • 对于流程的控制权
  • 对于创建依赖对象的控制权
  • 对于绑定依赖对象的控制权

作用: 引导开发人员降低代码耦合度,常与 DIP 配合使用。


依赖倒置原则:(Dependency inversion principle)高级模块不应该依赖于低级模块。两者都应该依赖于抽象;抽象不应该依赖实现细节,细节应该依赖抽象。

作用:引导开发人员降低代码耦合度


Note:

  1. 代码示例中出现过三种模式:静态工厂模式,抽象工厂模式,依赖注入(方法注入)
  2. 文中的各种原则的实现并不一定是该原则对应的最佳实践

参考资料:

1. https://www.tutorialsteacher.com/ioc
2. https://martinfowler.com/articles/injection.html
3. https://en.wikipedia.org/wiki/Single_responsibility_principle

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容