大话设计模式之依赖倒转模式

一、定义编程

  • 高层模块不该该依赖底层模块,两个都应该依赖于抽象
  • 抽象不该该依赖细节,细节应该依赖抽象

高层模块和低层模块容易理解,每个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,原子逻辑的再组装就是高层模块。那什么是抽象,什么又是细节呢?在Java语言中,抽象就是指接口或抽象类,二者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特色就是能够直接被实例化,也就是能够加上一个关键字new产生一个对象。依赖倒置原则在Java语言中的表现就是:架构

  • 模块间的依赖是经过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是经过接口或抽象类产生的;
  • 接口或抽象类不依赖于实现类;
  • 实现类依赖接口或抽象类。

     更加精简的定义就是“面向接口编程”——OOD(Object-Oriented Design,面向对象设计)的精髓之一。ide

二、里氏替换原则模块化

说明:一个软件的实体,若是使用的一个类是父类的话,那么必定适用于其子类,并且它察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化工具

里氏代换原则:(LSP):子类必须可以替换掉它们的父类型【ASD】单元测试

意思是:一件事情,父类能干,子类也必定要能干吗,若是不是那么子类就不该该继承父类。 测试

正有了这个原则使得继承复用成为了可能。只有当子类能够替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也可以在父类的基础上增长新的行为。spa

需求变化时,只须继承,而别的东西不会改变。因为里氏代换原则才使得开放封闭成为可能。这样使得子类在父类无需修改的话就能够扩展。这就是电脑坏了菜鸟也能够修,收音机坏了只有专家能修改的道理。电脑是模块化的,模块之间依赖性不强。而收音机依赖性很强,对以维护。线程

采用依赖倒置原则能够减小类间的耦合性,提升系统的稳定性,减小并行开发引发的风险,提升代码的可读性和可维护性。设计

三、言而无信,你太须要契约

     证实一个定理是否正确,有两种经常使用的方法:一种是根据提出的论题,通过一番论证,推出和定理相同的结论,这是顺推证法;还有一种是首先假设提出的命题是伪命题,而后推导出一个荒谬、与已知条件互斥的结论,这是反证法。咱们今天就用反证法来证实依赖倒置原则是多么的优秀和伟大!

     论题:依赖倒置原则能够减小类间的耦合性,提升系统的稳定性,减小并行开发引发的风险,提升代码的可读性和维护性。

     反论题:不使用依赖倒置原则也能够减小类间的耦合性,提升系统的稳定性,减小并行开发引发的风险,提升代码的可读性和维护性。

     咱们经过一个例子来讲明反论题是不成立的。如今的汽车愈来愈便宜了,也就顶多一个卫生间的价格就能够买到一辆不错的汽车,有汽车就必然有人来驾驶了,司机驾驶奔驰车的类图如图3-1所示。

                                         

                       

图3-1 司机驾驶奔驰车类图

     奔驰车能够提供一个方法run,表明车辆运行,实现过程如代码清代3-1所示。

1)司机代码:

public class Driver
    {
        public void drive(Benz benz)
        {
            benz.run();
        }
    }
View Code

2)奔驰汽车代码:

public class Benz
    {
        public void run()
        {
            Console.WriteLine("奔驰汽车运行");
        }
    }
View Code

   有车,有司机,在Client场景类产生相应的对象,其源代码如代码清代3-3所示。

public class Client
    {
        static void Main(string[] args)
        {
            Driver driver = new Driver();

            Benz benz = new Benz();
            driver.drive(benz);
        }
    }
View Code

     经过以上的代码,完成了司机开动奔驰车的场景,到目前为止,这个司机开奔驰车的项目没有任何问题。咱们常说“危难时刻见真情”,咱们把这句话移植到技术上就成了“变动才显真功夫”,业务需求变动永无休止,技术前进就永无止境,在发生变动时才能发觉咱们的设计或程序是不是松耦合。咱们在一段貌似磐石的程序上加上一块小石头:张三司机不只要开奔驰车,还要开宝马车,又该怎么实现呢?麻烦出来了,那好,咱们走一步是一步,咱们先把宝马车产生出来,实现过程如代码清单3-4所示。

public class BMW
    {
        public void run()
        {
            Console.WriteLine("宝马车开始运行");
        }
    }
View Code

宝马车也产生了,可是咱们却没有办法让张三开动起来,为何?张三没有开动宝马车的方法呀!一个拿有C照的司机居然只能开奔驰车而不能开宝马车,这也太不合理了!在现实世界都不容许存在这种状况,况且程序仍是对现实世界的抽象,咱们的设计出现了问题:司机类和奔驰车类之间是一个紧耦合的关系,其致使的结果就是系统的可维护性大大下降,可读性下降,两个类似的类须要阅读两个文件,你乐意吗?还有稳定性,什么是稳定性?固化的、健壮的才是稳定的,这里只是增长了一个车类就须要修改司机类,这不是稳定性,这是易变性。被依赖者的变动居然让依赖者来承担修改的成本,这样的依赖关系谁肯承担!证实到这里,咱们已经知道伪命题已经部分不成立了。

     注意 设计是否具有稳定性,只要适当的“松松土”,观察“设计的蓝图”是否还能够茁壮的成长就能够得出结论,稳定性较高的设计,在周围环境频繁变化的时候,依然能够作到“我自岿然不动”。

     咱们继续证实,“减小并行开发引发的风险”,什么是并行开发的风险?并行开发最大的风险就是风险扩散,原本只是一段程序的错误或异常,逐步波及一个功能,一个模块,甚至到最后毁坏了整个项目,为何并行开发就有这个风险呢?一个团队,20人开发,各人负责不一样的功能模块,甲负责汽车类的建造,乙负责司机类的建造,在甲没有完成的状况下,乙是不能彻底地编写代码的,缺乏汽车类,编译器根本就不会让你经过!在缺乏Benz类的状况下,Driver类能编译吗?更不要说是单元测试了!在这种不使用依赖倒置原则的环境中,全部的开发工做都是“单线程”的,甲作完,乙再作,而后是丙继续…,这在90年代“我的英雄主义”编程模式中仍是比较适用的,一我的完成全部的代码工做,但在如今的大中型项目中已是彻底不能胜任了,一个项目是一个团队的协做结果,一个“英雄”再牛X也不可能了解全部的业务和全部的技术,要协做就要并行开发,要并行开发就要解决模块之间的项目依赖关系,那而后呢?依赖倒置原则就隆重出场了!

根据以上证实,若是不使用依赖倒置原则就会加剧类间的耦合性,下降系统的稳定性,增长并行开发引发的风险,下降代码的可读性和维护性。承接上面的例子,引入依赖倒置原则后的类图如图3-2所示。

                                                      

                                                                        图3-2 引入依赖倒置原则后的类图

创建两个接口:IDriver和ICar,分别定义了司机和汽车的各个职能,司机就是驾驶汽车,必须实现drive()方法,其实现过程如代码清单3-5所示。

代码清单3-5 司机接口

public interface IDriver
    {
        void Drive(ICar car);
    }
View Code

接口只是一个抽象化的概念,是对一类事物的最抽象描述,具体的实现代码由相应的实现类来完成,Driver实现类如代码清单3-6所示。

代码清单3-6 司机类的实现

public class Driver : IDriver
    {
        public void Drive(ICar car)
        {
            car.run();
        }
    }
View Code

在IDriver中,经过传入ICar接口实现了抽象之间的依赖关系,Driver实现类也传入了ICar接口,至于究竟是哪一个型号的Car须要在高层模块中声明。

     ICar及其两个实现类的实现过程如代码清单3-7所示。

代码清单3-7 汽车接口及两个实现类

public interface ICar
    {
        void run();
    }
View Code
public class Benz:ICar
    {
        public void run()
        {
            Console.WriteLine("奔驰车开始运行");
        }
    }

public class BMW :ICar
    {
        public void run()
        {
            Console.WriteLine("宝马车开始运行");
        }
    }
View Code

在业务场景中,咱们贯彻“抽象不该该依赖细节”,也就是咱们认为抽象(ICar接口)不依赖BMW和Benz两个实现类(细节),所以咱们在高层次的模块中应用都是抽象,Client的实现过程如代码清单3-8所示

class Client
    {
        static void Main(string[] args)
        {
            IDriver driver = new Driver();

            ICar benz = new Benz();
            ICar bmw = new BMW();

            driver.Drive(benz);
            driver.Drive(bmw);
        }
    }
View Code

四、最佳实践

     依赖倒转原则的本质就是经过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,咱们怎么在项目中使用这个规则呢?只要遵循如下的几个规则就能够:

  • 每一个类尽可能都有接口或抽象类,或者抽象类和接口二者都具有。

     这是依赖倒置的基本要求,接口和抽象类都是属于抽象的,有了抽象才可能依赖倒置。

  • 变量的显示类型尽可能是接口或者是抽象类。

     不少书上说变量的类型必定要是接口或者是抽象类,这个有点绝对化了,好比一个工具类,xxxUtils通常是不须要接口或是抽象类的。还有,若是你要使用类的clone方法,就必须使用实现类,这个是JDK提供一个规范。

  • 任何类都不该该从具体类派生。

     若是一个项目处于开发状态,确实不该该有从具体类派生出的子类的状况,但这也不是绝对的,由于人都是会犯错误的,有时设计缺陷是在所不免的,所以只要不超过两层的继承都是能够忍受的。特别是作项目维护的同志,基本上能够不考虑这个规则,为何?维护工做基本上都是作扩展开发,修复行为,经过一个继承关系,覆写一个方法就能够修正一个很大的Bug,何须再要去继承最高的基类呢?

  • 尽可能不要覆写基类的方法。

     若是基类是一个抽象类,并且这个方法已经实现了,子类尽可能不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会产生必定的影响。

  • 结合里氏替换原则使用

     在上一个章节中咱们讲解了里氏替换原则,父类出现的地方子类就能出现,再结合本章节的讲解,咱们能够得出这样一个通俗的规则: 接口负责定义public属性和方法,而且声明与其余对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。

     讲了这么多,估计你们对“倒置”这个词仍是有点不理解,那到底什么是“倒置”呢?咱们先说“正置”是什么意思,依赖正置就是类间的依赖是实实在在的实现类间的依赖,也就是面向实现编程,这也是正常人的思惟方式,我要开奔驰车就依赖奔驰车,我要使用笔记本电脑就直接依赖笔记本电脑,而编写程序须要的是对现实世界的事物进行抽象,抽象的结果就是有了抽象类和接口,而后咱们根据系统设计的须要产生了抽象间的依赖,代替了人们传统思惟中的事物间的依赖,“倒置”就是从这里产生的。

     依赖倒置原则的优势在小型项目中很难体现出来,例如小于10我的月的项目,使用简单的SSH架构,基本上不费太大力气就能够完成,是否采用依赖倒置原则影响不大。可是,在一个大中型项目中,采用依赖倒置原则能够带来很是多的优势,特别是规避一些非技术因素引发的问题。项目越大,需求变化的几率也越大,经过采用依赖倒置原则设计的接口或抽象类对实现类进行约束,能够减小需求变化引发的工做量剧增的状况。人员的变更在大中型项目中也是时常存在的,若是设计优良、代码结构清晰,人员变化对项目的影响基本为零。大中型项目的维护周期通常都很长,采用依赖倒置原则可让维护人员轻松地扩展和维护。

     依赖倒置原则是六个设计原则中最难以实现的原则,它是实现开闭原则的重要途径,依赖倒置原则没有实现,就别想实现对扩展开放,对修改关闭。在项目中,你们只要记住是“面向接口编程”就基本上抓住了依赖倒转原则的核心。

     讲了这么多依赖倒置原则的优势,咱们也来打击一下你们,在现实世界中确实存在着必须依赖细节的事物,好比法律,就必须依赖细节的定义。 “杀人偿命”在中国的法律中古今有之,那这里的杀人就是一个抽象的含义,怎么杀,杀什么人,为何杀人,都没有定义,只要是杀人就通通得偿命,那这就是有问题了,好人杀了坏人,还要陪上本身的一条性命,这是不公正的,从这一点看,咱们在实际的项目中使用依赖倒置原则时须要审时度势,不要抓住一个原则不放,每个原则的优势都是有限度的,并非放之四海而皆准的真理,因此别为了遵循一个原则而放弃了一个项目的终极目标:投产上线和盈利。做为一个项目经理或架构师,应该懂得技术只是实现目的的工具,惹恼了顶头上司,设计作得再漂亮,代码写得再完美,项目作得再符合标准,一旦项目亏本,产品投入大于产出,那总体就是扯淡!你本身也别想混得更好!

相关文章
相关标签/搜索