C#设计模式六大原则 - 依赖倒置

文章目录

    • 依赖倒置原则(DIP)
      • 一、举个栗子
      • 二、衍生思考


  • 单一职责原则(Single Reponsibility Principle,SRP)
  • 里氏替换原则(Liskov Substitution Principle,LSP)
  • 依赖倒置原则(Dependence Inversion Principle,DIP)
  • 接口隔离原则(Interface Segregation Principe,ISP)
  • 迪米特法则(Law of Demeter,LOD)
  • 开闭原则(Open Closed Principle,OCP)

依赖倒置原则(DIP)

Dependence Inversion Principle,简称:DIP。

高层模块不应该依赖低层模块,两者都应该依赖其抽象,不要依赖细节

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

问题由来:类 classA 直接依赖类 classB ,假如要将类 classA 改为依赖类 classC ,则必须通过修改类 classA 的代码来达成。这种场景下,类 classA 一般是高层模块,负责复杂的业务逻辑;类 classB 和类 classC 是低层模块,负责基本的原子操作;如若修改类 classA ,会给程序带来不必要的风险。

解决方案:将类 classA 修改为依赖接口 Interface,类 classB 和类 classC 各自实现接口 Interface ,类 classA 通过接口 Interface 间接与类 classB 或者类 classC 发生联系,则会大大降低修改类 classA 的几率。


在C#中,抽象就是指接口或者抽象类,两者都不能直接进行实例化;细节就是实现类,就是实现了接口或继承了抽象类而产生的类就是实现类,可以直接被实例化。所谓的高层与低层,每个逻辑实现都是由原始逻辑组成,原始逻辑就属于低层模块,像我们常说的三层架构,业务逻辑层相对数据层,数据层就属于低层模块,业务逻辑层就属于高层模块,是相对来说的。

依赖倒置原则就是程序逻辑在传递参数或关联关系时,尽量引用高层次的抽象,不使用具体的类,即使用接口或抽象类来引用参数,声明变量以及处理方法返回值等。

这样就要求具体的类就尽量不要有多余的方法,否则就调用不到。说简单点,就是“面向接口编程”。

论题:依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性。

一、举个栗子

还是举一个学生使用手机的例子吧,代码如下:
学生类:

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

    /// 
    /// 依赖抽象
    /// 
    /// 
    public void Play(AbstractPhone phone)
    {
        Console.WriteLine("这里是{0}", this.Name);
        phone.Call();
        phone.Text();
    }
}

手机抽象类:

//手机抽象类
public abstract class AbstractPhone
{
    public int Id { get; set; }
    public string Branch { get; set; }
    public abstract void Call();
    public abstract void Text();
}

苹果手机类,继承手机抽象类 AbstractPhone

//苹果手机 继承 AbstractPhone
public class iPhone : AbstractPhone
{
	//打电话
    public override void Call()
    {
        Console.WriteLine("User {0} Call", this.GetType().Name);
    }
    //发短信
    public override void Text()
    {
        Console.WriteLine("User {0} Call", this.GetType().Name);
    }
}

荣耀手机类,同样继承手机抽象类 AbstractPhone

//荣耀手机 继承 AbstractPhone
public class Honor : AbstractPhone
{
	//打电话
    public override void Call()
    {
        Console.WriteLine("User {0} Call", this.GetType().Name);
    }
	//发短信
    public override void Text()
    {
        Console.WriteLine("User {0} Call", this.GetType().Name);
    }
}

调用程序:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Student student = new Student()
            {
                Id = 66,
                Name = "柯南"
            };
            {
                AbstractPhone phone = new iPhone();
                student.Play(phone);
            }
            {
                AbstractPhone honor = new Honor();
                student.Play(honor);
            }
   	     }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        Console.Read();
    }
}

该例子中,到目前为止,项目没有任何问题。我们常说“危难时刻见真情”,把这句话移植到技术上就成了“变更才显真功夫”,业务绣球变更永无休止,技术前进永无止境,在发生变更时才能发觉我们的设计或程序是否是松耦合。

因此,以上项目中,手机类的声明都以做抽象处理,声明学生实例时依赖于细节,想用iphone还是honor,只需修改new后方的细节即可,它对低层模块的依赖都建立在抽象上了。

然而,倘若一位老师想要打电话发短信,那此处得再写一个Teacher类,而Teacher类的Play()方法和Student代码出现冗余,因此增加了代码的不稳定性。

因此,此处的Student类模块可以参考于手机类的接口一样设计,新增加Person抽象类和Teacher实体类,修改之后,代码如下:

public abstract class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public abstract void Play(AbstractPhone phone);
}

public class Student : Person
{
    /// 
    /// 依赖抽象
    /// 
    /// 
    public override void Play(AbstractPhone phone)
    {
        Console.WriteLine("这里是{0}{1}", this.Name, this.GetType().Name);
        phone.Call();
        phone.Text();
    }
}

public class Teacher: Person
{
    /// 
    /// 依赖抽象
    /// 
    /// 
    public override void Play(AbstractPhone phone)
    {
        Console.WriteLine("这里是{0}{1}", this.Name, this.GetType().Name);
        phone.Call();
        phone.Text();
    }
}

调用程序:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Person teacher = new Teacher()
            {
                Id = 66,
                Name = "柯南"
            };
            {
                AbstractPhone phone = new iPhone();
                teacher.Play(phone);
            }
            {
                AbstractPhone honor = new Honor();
                teacher.Play(honor);
            }
   	     }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        Console.Read();
    }
}

这样就实现了代码的去细节。

同时,负责Student类与负责Teacher类的开发人员,就可以独立开发了,而且项目之间的单元测试也可以独立地运行,而TDD(Test-Driven Development,测试驱动开发)开发模式就是依赖倒置原则的最高级应用。

二、衍生思考

依赖倒置原则的应用可以很多,其中一种的修改即使等式的右方

  • 在 AbstractPhone phone = new Honor()的等号右方还可以以其他方式生成,通过 反射+工厂模式,从而实现依赖注入
  • 在 .Net Core 中,使用泛型主机 (IHostBuilder)时,就用到了类型注入 Startup 类

你可能感兴趣的:(#,设计模式,c#,设计模式)