【学习笔记】设计模式六大原则之依赖倒置原则、接口隔离原则和开放封闭原则

一、依赖倒置原则(Dependence Inversion Principle)

下面我们来看个示例:

/// <summary>
/// 学生
/// </summary>
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

    //依赖细节  高层就依赖了底层
    public void PlayHonor(Honor phone)
    {
        Console.WriteLine("这里是{0}", this.Name);
        phone.Call();
        phone.SendSMS();
    }

    //依赖细节  高层就依赖了底层
    public void PlayOppo(Oppo phone)
    {
        Console.WriteLine("这里是{0}", this.Name);
        phone.Call();
        phone.SendSMS();
    }
}

/// <summary>
/// 荣耀手机
/// </summary>
public class Honor
{
    /// <summary>
    /// 打电话
    /// </summary>
    public void Call()
    {
        Console.WriteLine("Use {0} Call", this.GetType().Name);
    }

    /// <summary>
    /// 发短信
    /// </summary>
    public void SendSMS()
    {
        Console.WriteLine("Use {0} SendSMS", this.GetType().Name);
    }
}

/// <summary>
/// Oppo手机
/// </summary>
public class Oppo
{
    /// <summary>
    /// 打电话
    /// </summary>
    public void Call()
    {
        Console.WriteLine("Use {0} Call", this.GetType().Name);
    }

    /// <summary>
    /// 发短信
    /// </summary>
    public void SendSMS()
    {
        Console.WriteLine("Use {0} SendSMS", this.GetType().Name);
    }
}

在示例中Student类内部使用了Honor类和Oppo类,这里的使用者Student类就称为高层模块,被使用者Honor类和Oppo类就称为低层模块,这时候我们的高层模块就依赖于我们的低层模块。

那么这样子有什么不好呢?

  1、如果此时我们的低层模块Honor类和Oppo类扩展了或者是发生变动了,则就有可能需要修改Student类,这就导致我们的Student类难以维护,Honor类和Oppo类扩展困难。

  2、面向对象语言开发,就是类与类之间进行交互,如果高层直接依赖低层的细节,细节是多变的,那么低层的变化就导致上层的变化;如果层数多了,低层的修改会直接水波效应传递到最上层,一点细微的改动都会导致整个系统从下往上的修改。

那么我们怎么才能更好的解决这个问题呢?

  竟然我们的低层模块是多变的,那么我们的高层模块就不应该依赖于低层模块,二者都应该依赖于抽象

下面我们来改造一下:

/// <summary>
/// 学生
/// </summary>
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

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

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

/// <summary>
/// 手机抽象类
/// </summary>
public abstract class AbstractPhone
{
    public int Id { get; set; }
    public string Branch { get; set; }

    /// <summary>
    /// 打电话
    /// </summary>
    public abstract void Call();

    /// <summary>
    /// 发短信
    /// </summary>
    public abstract void SendSMS();
}

/// <summary>
/// 荣耀手机
/// </summary>
public class Honor : AbstractPhone
{
    /// <summary>
    /// 打电话
    /// </summary>
    public override void Call()
    {
        Console.WriteLine("Use {0} Call", this.GetType().Name);
    }

    /// <summary>
    /// 发短信
    /// </summary>
    public override void SendSMS()
    {
        Console.WriteLine("Use {0} SendSMS", this.GetType().Name);
    }
}

/// <summary>
/// Oppo手机
/// </summary>
public class Oppo : AbstractPhone
{
    /// <summary>
    /// 打电话
    /// </summary>
    public override void Call()
    {
        Console.WriteLine("Use {0} Call", this.GetType().Name);
    }

    /// <summary>
    /// 发短信
    /// </summary>
    public override void SendSMS()
    {
        Console.WriteLine("Use {0} SendSMS", this.GetType().Name);
    }
}

经过这样子改造之后,我们的高层模块Student就不直接依赖于低层模块了,而是通过抽象AbstractPhone类来依赖的。

这样子做有什么好处呢?

  1、一个方法满足不同类型的参数。

  2、还支持扩展,只要是实现了这个抽象,不用修改Student。

  3、面向抽象,抽象一般是稳定的,那低层细节的变化或者是扩展就不会影响到高层,这样就能支持层内部的横向扩展,不会影响其他地方,这样的程序架构就是稳定的。

小结:

1、依赖倒置原则:高层模块不应该依赖于低层模块,二者都应该依赖于抽象。应该依赖于抽象,而不是依赖细节。

2、抽象:接口/抽象类,可以包含没有实现的元素。细节:普通类,一切都是确定的。

3、面向抽象编程:尽量的使用抽象,80%的设计模式都是跟抽象有关。

4、面向抽象不止一个类型,用的就是通用功能;非通用的,那就不应该面向抽象。

5、面向抽象,只要抽象不变,高层就不变。

6、面向对象语言开发,就是类与类之间进行交互,如果高层直接依赖低层的细节,细节是多变的,那么低层的变化就导致上层的变化;如果层数多了,低层的修改会直接水波效应传递到最上层,一点细微的改动都会导致整个系统从下往上的修改。

7、面向抽象,抽象一般是稳定的,那低层细节的变化或者是扩展就不会影响到高层,这样就能支持层内部的横向扩展,不会影响其他地方,这样的程序架构就是稳定的。

8、依赖倒置原则(理论基础),IOC控制反转(实践封装),DI依赖注入(实现IOC的手段)。

二、接口隔离原则(Interface Segregation Principle)

 接口隔离原则:客户端不应该依赖于它不需要的接口; 一个类对另一个类的依赖应该建立在最小的接口上。

怎么理解这个呢?下面我们来看个示例:

/// <summary>
/// 手机抽象类
/// </summary>
public abstract class AbstractPhone
{
    public int Id { get; set; }
    public string Branch { get; set; }

    /// <summary>
    /// 打电话
    /// </summary>
    public abstract void Call();

    /// <summary>
    /// 发短信
    /// </summary>
    public abstract void SendSMS();

    /// <summary>
    /// 拍照
    /// </summary>
    public abstract void Photo();
}

可以看出我们在上面的基础上加了一个拍照功能,我们都知道现在的智能手机都有拍照功能,但是呢老旧的诺基亚手机就没有拍照功能,所以说这样子写就是不合理的。

此时我们就需要对其进行拆分了:

/// <summary>
/// 手机抽象类
/// </summary>
public abstract class AbstractPhone
{
    public int Id { get; set; }
    public string Branch { get; set; }

    /// <summary>
    /// 打电话
    /// </summary>
    public abstract void Call();

    /// <summary>
    /// 发短信
    /// </summary>
    public abstract void SendSMS();
}

/// <summary>
/// 拍照接口
/// </summary>
public interface IPhoto
{
    /// <summary>
    /// 拍照
    /// </summary>
    void Photo();
}

这样子改造后就符合一个类对另一个类的依赖建立在最小的接口上。

小结:

1、接口隔离原则:客户端不应该依赖于它不需要的接口; 一个类对另一个类的依赖应该建立在最小的接口上。

2、接口interface定义 can do   不局限产品。

3、接口到底该如何定义?

  既不能是大而全,会强迫实现没有的东西,也会依赖自己不需要的东西。

  也不能太细一个方法一个接口,这样面向抽象就没有意义了。

  按照功能的密不可分来定义接口,而且应该是动态的,随着业务发展会有变化的,但是在设计的时候,要留好提前量,避免抽象的变化。

4、接口合并,业务细节要尽量的内聚,接口不要暴露太多业务细节(即接口不能太细了)。例如:

  1、地图Map--定位/搜索/导航 ,这种属于固定步骤,就可以合并成一个接口。

  2、手机--打电话/发短信,只要是手机这2个功能就都会有,那么我们就应该合并成一个接口。

三、开放封闭原则(Open Closed Principle)

小结:

1、开放封闭原则:对扩展开放,对修改封闭。

  修改:修改现有代码(类)(例如:程序分支。)

  扩展:增加代码(类)(例如:之前提到的单一职责原则。)

2、面向对象语言是一种静态语言,最害怕变化,会波及很多东西而导致全面测试。最理想就是新增类,对原有代码没有改动,原有的代码才是可信的。

3、其他5个原则的建议,就是为了更好的做到OCP,开闭原则也是面向对象语言开发一个终极目标。

4、如果有功能增加/修改的需求:修改现有方法(最不理想的)---增加方法(相对较好的)---增加类(比较理想的)---增加/替换类库(最理想的)。

原文地址:https://www.cnblogs.com/xyh9039/p/12723520.html