1. 定义
依赖倒置原则(Dependence Inversion Principle DIP):在程序设计时,高层模块不应该依赖于低层模块,二者应该通过抽象依赖,即依赖抽象,而不是依赖细节,换言之,程序设计应对接口编程,而不是针对实现编程。即包含以下三层含义:
1)高层模块不应该依赖低层模块,两者都应该依赖其抽象;
2)抽象不应该依赖细节;
3)细节应该依赖抽象
每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,原子逻辑的再组装就是高层模块。
抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,也就是可以加上一个关键字new产生一个对象。
依赖倒置原则在编程语言中表现就是:
1)模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
2)接口或抽象类不依赖于实现类;
3)实现类依赖接口或抽象类
2. 为什么要实现依赖倒置
2.1 实现代码重用,避免大量重构代码,比如:当切换数据库时,我们无需考虑ui层和bll层,只需考虑dal层,无需重构代码
2.2 可扩展性强,架构稳定
2.3 类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性
3.依赖的三种写法
3.1 构造函数传递依赖对象
在类中通过构造函数声明依赖对象,按照依赖注入的说法,这种方式叫做构造函数注入
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace helloworld
{
class Program
{
static void Main(string[] args)
{
biuck biuck = new biuck();
Driver driver = new Driver(biuck);
driver.drive();
}
}
public interface IDriver
{
public void drive();
}
public interface ICar
{
//是汽车就应该能跑
public void run();
}
public class Driver : IDriver
{
private ICar car;
//构造函数注入
public Driver(ICar _car)
{
this.car = _car;
}
//司机的主要职责就是驾驶汽车
public void drive()
{
this.car.run();
}
}
public class biuck : ICar
{
public void run()
{
Console.WriteLine("run");
}
}
}
3.2 Setter方法传递依赖对象
在抽象中设置Setter方法声明依赖关系,依照依赖注入的说法,这是Setter依赖注入
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace helloworld
{
class Program
{
static void Main(string[] args)
{
biuck biuck = new biuck();
Driver driver = new Driver();
driver.setCar(biuck);
driver.drive();
}
}
public interface IDriver
{
public void drive();
public void setCar(ICar car);
}
public interface ICar
{
//是汽车就应该能跑
public void run();
}
public class Driver : IDriver
{
private ICar Car;
public void drive()
{
this.Car.run();
}
public void setCar(ICar car)
{
this.Car = car;
}
}
public class biuck : ICar
{
public void run()
{
Console.WriteLine("run");
}
}
}
3.3 接口声明依赖对象
在接口的方法中声明依赖对象,该方法也叫做接口注入。
4.最佳实践
依赖倒置原则是6个设计原则中最难以实现的原则,它是实现开闭原则的重要途径,依赖倒置原则没有实现,就别想实现对扩展开放,对修改关闭。在项目中,大家只要记住是“面向接口编程”就基本上抓住了依赖倒置原则的核心。
依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。需要遵循以下几个规则:
4.1 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
这是依赖倒置的基本要求,接口和抽象类都是属于抽象的,有了抽象才可能依赖倒置。
4.2 变量的表面类型尽量是接口或者是抽象类
4.3 任何类都不应该从具体类派生
4.4 尽量不要覆写基类的方法
4.5 结合里氏替换原则使用
4. 如何实现依赖倒置
3.1 从实现类与抽象类的角度进行分析,在运用具体实现类与实现抽象类时,需要保证引用“基类”之处可以修改成其子类;
3.2 从层次关系角度进行分析,需要定义清晰的层次关系,使每个层次通过接口的方式进行;
3.3 从对象构造角度进行分析,如果创建的是动态的对象,则使用依赖倒转,如果创建的是一些静态的具体类并且变化率很低,则无须创建基类去继承,换言之,当细节多变化时,则要使用依赖倒置,反之,后者。
4. 代码实现【来源于网络教程】
4.1 设计
4.2 DIP
using System;
using IBLL;
using Factory;
using IDAL;
namespace DIP
{
class Program
{
static void Main(string[] args)
{
IStudentService studentService = SimpleFactory.CreateStudentService();
AbstractPhone iphone = SimpleFactory.CreatePhoneService();
studentService.Study();
studentService.Play(iphone);
Console.ReadKey();
}
}
}
4.3 BLL
using IBLL;
using IDAL;
using System;
namespace BLL
{
/// <summary>
/// 业务逻辑层,完成对业务逻辑的封装
/// </summary>
public class StudentService : IStudentService
{
public override void Play(AbstractPhone phone)
{
phone.Call();
phone.Text();
}
public override void Study()
{
Console.WriteLine("学习DIP,依赖倒置原则!");
}
}
}
4.3 DAL
using IDAL;
using System;
namespace DIP.DAL
{
public class Honer:AbstractPhone
{
public override void Call()
{
Console.WriteLine("使用华为打电话");
}
public override void Text()
{
Console.WriteLine("使用华为写作业");
}
}
}
using IDAL;
using System;
namespace DIP.DAL
{
public class Iphone : AbstractPhone
{
public override void Call()
{
Console.WriteLine("使用iphone打电话");
}
public override void Text()
{
Console.WriteLine("使用iphone写作业");
}
}
}
IBLL
using IDAL;
namespace IBLL
{
public abstract class IStudentService
{
/// <summary>
/// 学习
/// </summary>
public abstract void Study();
/// <summary>
/// 玩
/// </summary>
public abstract void Play(AbstractPhone phone);
}
}
IDAL
namespace IDAL
{
public abstract class AbstractPhone
{
public abstract void Call();
public abstract void Text();
}
}
SimpleFactory
using IBLL;
using System;
using System.Reflection;
using System.Configuration;
using IDAL;
namespace Factory
{
/// <summary>
/// 工厂类
/// </summary>
public class SimpleFactory
{
//读取配置文件
private static string IStudentServiceAssembly = ConfigurationManager.AppSettings["IStudentServiceAssembly"];
private static string PhoneServiceAssembly = ConfigurationManager.AppSettings["AbstractPhoneAssembly"];
/// <summary>
/// 反射
/// </summary>
/// <returns></returns>
public static IStudentService CreateStudentService()
{
//加载程序集
Assembly assembly = Assembly.Load(IStudentServiceAssembly.Split(',')[1]);
Type type = assembly.GetType(IStudentServiceAssembly.Split(',')[0]);
//获取实例
return (IStudentService)Activator.CreateInstance(type);
}
public static AbstractPhone CreatePhoneService()
{
Assembly assembly = Assembly.Load(PhoneServiceAssembly.Split(',')[1]);
Type type = assembly.GetType(PhoneServiceAssembly.Split(',')[0]);
return (AbstractPhone)Activator.CreateInstance(type);
}
}
}
app.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<appSettings>
<add key="IStudentServiceAssembly" value="DIP.BLL.StudentService,DIP.BLL"/>
<add key="AbstractPhoneAssembly" value="DIP.DAL.Honer,DIP.DAL"/>
</appSettings>
</configuration>
结果: