面向对象编程原则(06)——依赖倒转原则

Posted 谷哥的小弟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象编程原则(06)——依赖倒转原则相关的知识,希望对你有一定的参考价值。


版权声明

  • 本文原创作者:谷哥的小弟
  • 作者博客地址:http://blog.csdn.net/lfdfhl

参考资料

  1. 《大话设计模式》 作者:程杰
  2. 《Java设计模式》 作者:刘伟
  3. 《图解设计模式》 作者:结城浩
  4. 《重学Java设计模式》 作者:付政委
  5. 《Head First设计模式》作者:埃里克·弗里曼

依赖倒转原则概述

依赖倒转原则(Dependence Inversion Principle,DIP)是RobertCMartin在1996年为C++Reporter所写的专栏EngineeringNotebook的第三篇,后来加入到他在2002年出版的经典著作《Agile Software Development,Principles,Patterns,and Practices》一书中。如果说开闭原则是面向对象设计的目标,那么依赖倒转原则就是面向对象设计的主要实现机制之一,它是系统抽象化的具体实现。

该原则定义如下:

高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

High level modules should not depend upon low level modulesboth should depend upon abstractions. Abstractions should not depend upon detailsdetails
should depend upon abstractions.

简单来说:Program to an interface not an implementation;依赖倒转原则要求面向接口编程,而不要针对实现编程。

依赖倒转原则要求在程序代码中传递参数时或在关联关系中尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。

在引人抽象层后系统将具有很好的灵活性。在程序中尽量使用抽象层进行编程而将具体类写在配置文件中。这样如果系统行为发生变化,只需要对抽象层进行扩展,并修改配詈文件而无须修改原有系统的源代码。在不修改的情况下来扩展系统的功能并满足开闭原则的要求。

在实现依赖倒转原则时需要针对抽象层编程而将具体类的对象通过依赖注入(Dependence Injection;DI)的方式注人到其它对象中。依赖注人是指当一个对象要与其他对象发生依赖关系时采用抽象的形式来注人所依赖的对象。常用的注人方式有3种,分别是构造注入、设值注入(Setter注入)和接口注入。构造注入是指通过构造函数来传入具体类的对象;设值注入是指通过Setter方法来传入具体类的对象;而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象由子类对象来覆盖父类对象。

依赖倒转原则案例1

在此,以案例形式介绍依赖倒转原则。

软件公司开发人员在开发CRM系统时发现:该系统经常需要将存储在TXT或Excel文件中的客户信息转存到数据库中,因此需要进行数据格式转换。在客户数据操作类CustomerDAO中将调用数据格式转换类的方法来实现格式转换,设计方案结构如下:

初步实践之后发现方案存在一个非常严重的问题:由于每次转换数据时数据来源不一定相同,因此需要经常更换数据转换类。例如,有时候需要将TXTDataConvertor改为ExcelDataConvertor;一旦改调用类,此时需要就修改CustomerDAO的源代码。除此以外,在引入并使用新的数据转换类时也不得不修改CustomerDAO的源代码。由此而来,系统扩展性较差违反了开闭原则,重构如下:

  • 1、创建抽象类DataConvertor
  • 2、TXTDataConvertor和ExcelDataConvertor继承自DataConvertor
  • 3、CustomerDAO类中的引用为DataConvertor
  • 4、CustomerDAO类中的具体类型读取自配置文件config.xml

在上述重构过程中同时使用了开闭原则、里氏代换原则和依赖倒转原则,在大多数情况下这3个设计原则会同时出现:开闭原则是目标,里氏替换原则是基础,依赖倒转原则是手段;它们相辅相成、相互补充、目标一致;只是分析问题时所站的角度不同而已。

依赖倒转原则案例2

在本例中,我们通过电脑的组装过程来体会依赖倒转原则。假设电脑的组装需要英特尔芯片,希捷硬盘,金士顿内存等器件。

版本1

IntelCpu

package com.dip01;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public class IntelCpu 
    public void run() 
        System.out.println("Intel处理器在运行");
    


KingstonMemory

package com.dip01;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public class KingstonMemory 
	private String data;
	// 金士顿内存存数据
    public void save(String data) 
    	this.data = data;
        System.out.println("使用金士顿内存存储数据,data=" + data);
    

	// 金士顿内存读数据
    public String get() 
    	String data = this.data;
        System.out.println("获取金士顿内存中的数据,data=" + data);
        return data;
    


XiJieHardDisk

package com.dip01;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public class XiJieHardDisk 
	private String data;

	// 希捷硬盘存数据
    public void save(String data) 
    	this.data = data;
        System.out.println("使用希捷硬盘存储数据,data=" + data);
    

	// 希捷硬盘读数据
    public String get() 
    	String data = this.data;
        System.out.println("获取希捷硬盘中的数据,data=" + data);
        return data;
    


Computer

package com.dip01;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public class Computer 
    private XiJieHardDisk hardDisk;
    private IntelCpu cpu;
    private KingstonMemory memory;
    
    public Computer() 
		
	
    
	public Computer(XiJieHardDisk hardDisk, IntelCpu cpu, KingstonMemory memory) 
		this.hardDisk = hardDisk;
		this.cpu = cpu;
		this.memory = memory;
	



	public XiJieHardDisk getHardDisk() 
        return hardDisk;
    

    public void setHardDisk(XiJieHardDisk hardDisk) 
        this.hardDisk = hardDisk;
    

    public IntelCpu getCpu() 
        return cpu;
    

    public void setCpu(IntelCpu cpu) 
        this.cpu = cpu;
    

    public KingstonMemory getMemory() 
        return memory;
    

    public void setMemory(KingstonMemory memory) 
        this.memory = memory;
    

    public void run() 
        System.out.println("计算机开始工作");
        hardDisk.save("Hello World");
        hardDisk.get();
        cpu.run();
        memory.save("10101010");
        memory.get();
    


Test

package com.dip01;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public class Test 
    public static void main(String[] args) 
    	// 创建各个配件
        XiJieHardDisk hardDisk = new XiJieHardDisk();
        IntelCpu cpu = new IntelCpu();
        KingstonMemory memory = new KingstonMemory();
        
        // 组装计算机
        Computer computer = new Computer();
        computer.setCpu(cpu);
        computer.setHardDisk(hardDisk);
        computer.setMemory(memory);

        // 启动计算机
        computer.run();
    


测试结果:

小结

在该示例中,电脑的组装只能依靠特定品牌的组件,例如:英特尔芯片,希捷硬盘,金士顿内存等等。假若,换做其它芯片、硬盘、内存条就不再兼容了。所以,该示例依赖于与具体而不依赖于抽象,这明显违背了依赖倒转原则。

版本2

Cpu

package com.dip02;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public interface Cpu 
    void run();


IntelCpu

package com.dip02;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public class IntelCpu implements Cpu
	// 实现接口中的方法
	@Override
    public void run() 
        System.out.println("Intel处理器在运行");
    


Memory

package com.dip02;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public interface Memory 
	void save(String data);
	String get();

KingstonMemory

package com.dip02;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public class KingstonMemory implements Memory
	private String data;

	// 实现接口中的方法
	// 金士顿内存存数据
	@Override
    public void save(String data) 
    	this.data = data;
        System.out.println("使用金士顿内存存储数据,data=" + data);
    

	// 实现接口中的方法
	// 金士顿内存读数据
	@Override
    public String get() 
    	String data = this.data;
        System.out.println("获取金士顿内存中的数据,data=" + data);
        return data;
    


HardDisk

package com.dip02;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public interface HardDisk 
	void save(String data);
	String get();


XiJieHardDisk

package com.dip02;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public class XiJieHardDisk implements HardDisk
	private String data;

	// 实现接口中的方法
	// 希捷硬盘存数据
	@Override
    public void save(String data) 
    	this.data = data;
        System.out.println("使用希捷硬盘存储数据,data=" + data);
    

	// 实现接口中的方法
	// 希捷硬盘读数据
	@Override
    public String get() 
    	String data = this.data;
        System.out.println("获取希捷硬盘中的数据,data=" + data);
        return data;
    


Computer

package com.dip02;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public class Computer 
    private HardDisk hardDisk;
    private Cpu cpu;
    private Memory memory;
    
    public Computer() 
		
	
    
	public Computer(XiJieHardDisk hardDisk, IntelCpu cpu, KingstonMemory memory) 
		this.hardDisk = hardDisk;
		this.cpu = cpu;
		this.memory = memory;
	



	public HardDisk getHardDisk() 
        return hardDisk;
    

    public void setHardDisk(HardDisk hardDisk) 
        this.hardDisk = hardDisk;
    

    public Cpu getCpu() 
        return cpu;
    

    public void setCpu(Cpu cpu) 
        this.cpu = cpu;
    

    public Memory getMemory() 
        return memory;
    

    public void setMemory(Memory memory) 
        this.memory = memory;
    

    public void run() 
        System.out.println("计算机开始工作");
        hardDisk.save("Hello World");
        hardDisk.get();
        cpu.run();
        memory.save("10101010");
        memory.get();
    


Test

package com.dip02;
/**
 * 原创作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
public class Test 
    public static void main(String[] args) 
    	// 创建各个配件
        HardDisk hardDisk = new XiJieHardDisk();
        Cpu cpu = new IntelCpu();
        Memory memory = new KingstonMemory();
        
        // 组装计算机
        Computer computer = new Computer();
        computer.setCpu(cpu);
        computer.setHardDisk(hardDisk);
        computer.setMemory(memory);

        // 启动计算机
        computer.run();
    



测试结果:

以上是关于面向对象编程原则(06)——依赖倒转原则的主要内容,如果未能解决你的问题,请参考以下文章

设计模式面向对象设计原则之依赖倒转原则

面向对象设计原则之依赖倒转原则

设计模式-----依赖倒转原则

面向对象的设计模式之依赖倒置原则

设计模式七大原则之依赖倒转原则

面向对象编程设计原则