Java设计模式 七大原则(五)依赖倒置原则(Dependence Inversion Principle)

 

依赖倒置原则

依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

Shubho:这是SOLID原则里最后一个原则。这是海报

它的意思是:高层模块不应该依赖底层模块,两者都应该依赖其抽象

Shubho:考虑一个现实中的例子。你的汽车是由很多如引擎,车轮,空调和其它等部件组成,对吗?

Farhana:是的

Shubho:好,它们没有一个是严格的构建在一个单一单元里;换句话说,它们都是可插拔的,因此当引擎或车轮出问题时,你可以修理它(而不需要修理其它部件),甚至可以换一个。

在替换时,你仅需要确保引擎或车轮符合汽车的设计(如汽车能使用任何1500CC的引擎或任何18寸的车轮)。

当然,汽车也可能允许你在1500CC引擎的地方安装一个2000CC的引擎,事实上对某些制造商(如丰田汽车)是一样的。

现在,如果你的汽车的零部件不具备可插拔性会有什么不同?

Farhana:那会很可怕!因为如果汽车的引擎出故障了,你可能修理整部车或者需要买一个新的。

Shubho:是的,那么该如何做到"可插拔性"呢?

Farhana:这里抽象是关键,对吗?

Shubho:是的,在现实中,汽车是高级模块或实体,它依赖于低级模块或实体,如引擎或车轮。

相比直接依赖于引擎或车轮,汽车应依赖于某些抽象的有规格的引擎或车轮,以便于如果任何引擎或车轮符合抽象,那么它们都能组合到汽车中,汽车也能跑动。

一起看下面的类图

Shubho:注意到上面Car类有两个属性,它们都是抽象类型(接口)。引擎和车轮是可插拔的,因为汽车能接受任何实现了声明接口的对象,并且Car类不需要做任何改动。

Farhana:所以,如果代码中不用依赖倒置,我们将面临如下风险:

使用低级类会破环高级代码;

当低级类变化时需要很多时间和代价来修改高级代码;

产生低复用的代码;

Shubho:你完全掌握了,亲爱的!

1.概念: 

依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

2.依赖倒转原则用处: 

有些时候为了代码复用,一般会把常用的代码写成函数或类库。这样开发新项目时,直接用就行了。比如做项目时大多要访问数据库,所以我们就把访问数据库的代码写成了函数。每次做项目去调用这些函数。那么我们的问题来了。我们要做新项目时,发现业务逻辑的高层模块都是一样的,但客户却希望使用不同的数据库或存储住处方式,这时就出现麻烦了。我们希望能再次利用这些高层模块,但高层模块都是与低层的访问数据库绑定在一起,没办法复用这些高层模块。所以不管是高层模块和低层模块都应该依赖于抽象,具体一点就是接口或抽象类,只要接口是稳定的,那么任何一个更改都不用担心了。

3.注意事项: 

  • 高层模块不应该依赖低层模块。两个都应该依赖抽象。
  • 抽象不应该依赖结节。细节应该依赖抽象。

4.模拟场景: 

场景:

假设现在需要一个Monitor工具,去运行一些已有的APP,自动化来完成我们的工作。Monitor工具需要启动这些已有的APP,并且写下Log。

代码实现1:

namespace TestLibrary.ExtensionsClass
{
    using System;

    public class AppOne
    {
        public bool Start()
        {
            Console.WriteLine("1号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("1号APP输出日志");
            return true;
        }
    }

    public class AppTwo
    {
        public bool Start()
        {
            Console.WriteLine("2号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("2号APP输出日志");
            return true;
        }
    }

    public class Monitor
    {
        public enum AppNumber
        {
            AppOne=1,
            AppTwo=2
        }

        private AppOne appOne = new AppOne();
        private AppTwo appTwo = new AppTwo();
        private AppNumber number;
        public Monitor(AppNumber number)
        {
            this.number = number;
        }

        public bool StartApp()
        {
            return number == AppNumber.AppOne ? appOne.Start() : appTwo.Start();
        }

        public bool ExportAppLog()
        {
            return number == AppNumber.AppOne ? appOne.ExportLog() : appTwo.ExportLog();
        }
    }
}

复制代码

代码解析1:

在代码实现1中我们已经轻松实现了Monitor去运行已有APP并且写下LOG的需求。并且代码已经上线了.

春...夏...秋...冬...

春...夏...秋...冬...

春...夏...秋...冬...

就这样,三年过去了。

一天客户找上门了,公司业务扩展了,现在需要新加3个APP用Monitor自动化。这样我们就必须得改Monitor。

代码实现2:

namespace TestLibrary.ExtensionsClass
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    public class Monitor
    {
        public enum AppNumber
        {
            AppOne = 1,
            AppTwo = 2,
            AppThree = 3,
            AppFour = 4,
            AppFive = 5
        }

        private AppOne appOne = new AppOne();
        private AppTwo appTwo = new AppTwo();
        private AppThree appThree = new AppThree();
        private AppFour appFour = new AppFour();
        private AppFive appFive = new AppFive();
        private AppNumber number;
        public Monitor(AppNumber number)
        {
            this.number = number;
        }

        public bool StartApp()
        {
            bool result = false;
            if (number == AppNumber.AppOne)
            {
                result = appOne.Start();
            }
            else if (number == AppNumber.AppTwo)
            {
                result = appTwo.Start();
            }
            else if (number == AppNumber.AppThree)
            {
                result = appThree.Start();
            }
            else if (number == AppNumber.AppFour)
            {
                result = appFour.Start();
            }
            else if (number == AppNumber.AppFive)
            {
                result = appFive.Start();
            }

            return result;
        }

        public bool ExportAppLog()
        {
            bool result = false;
            if (number == AppNumber.AppOne)
            {
                result = appOne.ExportLog();
            }
            else if (number == AppNumber.AppTwo)
            {
                result = appTwo.ExportLog();
            }
            else if (number == AppNumber.AppThree)
            {
                result = appThree.ExportLog();
            }
            else if (number == AppNumber.AppFour)
            {
                result = appFour.ExportLog();
            }
            else if (number == AppNumber.AppFive)
            {
                result = appFive.ExportLog();
            }

            return result;
        }
    }
}

代码解析2:

这样会给系统添加新的相互依赖。并且随着时间和需求的推移,会有更多的APP需要用Monitor来监测,这个Monitor工具也会被越来越对的if...else撑爆炸,而且代码随着APP越多,越难维护。最终会导致Monitor走向灭亡(下线)。

介于这种情况,可以用Monitor这个模块来生成其它的程序,使得系统能够用在需要的APP上。OOD给我们提供了一种机制来实现这种“依赖倒置”。

代码实现3:


namespace TestLibrary.ExtensionsClass
{
    using System;

    public interface IApp
    {
        bool Start();
        bool ExportLog();
    }

    public class AppOne : IApp
    {
        public bool Start()
        {
            Console.WriteLine("1号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("1号APP输出日志");
            return true;
        }
    }

    public class AppTwo : IApp
    {
        public bool Start()
        {
            Console.WriteLine("2号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("2号APP输出日志");
            return true;
        }
    }

    public class Monitor
    {
        private IApp iapp;
        public Monitor(IApp iapp)
        {
            this.iapp = iapp;
        }

        public bool StartApp()
        {
            return iapp.Start();
        }

        public bool ExportAppLog()
        {
            return iapp.ExportLog();
        }
    }
}

代码解析3:

现在Monitor依赖于IApp这个接口,而与具体实现的APP类没有关系,所以无论再怎么添加APP都不会影响到Monitor本身,只需要去添加一个实现IApp接口的APP类就可以了。

猜你喜欢

转载自blog.csdn.net/Hurricane_m/article/details/89322034