浅谈C#六大设计原则

这篇文章主要介绍了C#六大设计原则的相关内容,文中代码非常细致,供大家参考和学习,感兴趣的朋友可以了解下

笔者作为一个菜鸟,会尝试以简单的代码和容易理解的语句去解释这几种原则的特性和应用场景。

这六种原则分别为单一职责原则、接口隔离原则、里氏替换原则、迪米特法则、依赖倒置原则、开闭原则。

单一职责原则

单一职责原则(SRP:Single responsibility principle),规定一个类中应该只有一个原因引起类的变化。

单一职责原则的核心就是解耦和增强内聚性。

问题:


 // 假设此类是数据库上下文
 public class DatabaseContext { }

 public class Test
 {
  private readonly DatabaseContext _context;
  public Test(DatabaseContext context)
  {
   _context = context;
  }

  // 用户登录
  public void UserLogin() { }

  // 用户注销
  public void UserLogout() { }

  // 新增一个用户
  public void AddUser() { }

  // 修改一个用户的信息
  public void UpdateUser() { }

  // 删除一个用户
  public void DeleteUser() { }
 }

Test 负责 职责 P1(用户登录和退出)和 P2(用户账号管理) 两个职责,当由于职责 P1 的需求发生变化而需要修改类时, 有可能会导致正常职责 P2 的功能发生故障。

上面的代码中,两个职责被耦合起来,担任了多种功能。

一个类中应该只有一个原因引起类的变化,也就要求一个类只应该负责一个功能,类中地代码是紧密联系的。

上面的示例代码非常简单,我们可以很自然地将一个个类分为两个部分。


 // 假设此类是数据库上下文
 public class DatabaseContext { }

 public class Test1
 {
  private readonly DatabaseContext _context;
  public Test1(DatabaseContext context)
  {
   _context = context;
  }

  // 用户登录
  public void UserLogin() { }

  // 用户注销
  public void UserLogout() { }

 }

 public class Test2
 {
  private readonly DatabaseContext _context;
  public Test2(DatabaseContext context)
  {
   _context = context;
  }
  // 新增一个用户
  public void AddUser() { }

  // 修改一个用户的信息
  public void UpdateUser() { }

  // 删除一个用户
  public void DeleteUser() { }
 }

因此,单一职责原则的解决方法,是将不同职责封装到不同的类或模块中。

接口隔离原则

接口隔离原则(ISP:Interface Segregation Principle) 要求对接口进行细分,类的继承建立在最小的粒度上,确保客户端继承的接口中,每一个方法都是它需要的。

笔者查阅了国外一些资料,大多将接口隔离原则定义为:

“Clients should not be forced to depend upon interfaces that they do not use.”

意思是不应强迫客户依赖于它不使用的方法

对于此原则的解释,这篇文章讲的非常透彻:

https://stackify.com/interface-segregation-principle/

这就要求我们拆分臃肿的接口成为更小的和更具体的接口,使得接口负责的功能更加单一。

目的:通过将软件分为多个独立的部分来减少所需更改的副作用和频率。

笔者想到从两方面论述:

其一,在描述多种动物时,我们可能会将不同种类的动物分类。但是这还不够,例如在鸟类中,我们印象中鸟的特征是鸟会飞,但是企鹅不会飞~。

那么还要对物种的特征进行细分,例如血液是什么颜色的、有没有脊椎等。

其二,我们可以通过下面代码表达:


 // 与登录有关
 public interface IUserLogin
 {
  // 登录
  void Login();

  // 注销
  void Logout();
 }

 // 与用户账号有关
 public interface IUserInfo
 {
  // 新增一个用户
  void AddUser();

  // 修改一个用户的信息
  void UpdateUser();

  // 删除一个用户
  void DeleteUser();
 }

上面的两个接口,各种实现不同的功能,彼此没有交叉,完美。

接下来我们看看两个继承了 IUserLogin 接口的代码


 // 对用户登录注销进行管理,资源准备和释放
 public class Test1 : IUserLogin
 {
  public void Login(){}

  public void Logout(){}
 }

 public class Test2 : IUserLogin
 {
  public void Login()
  {
   // 获取用户未读消息
  }

  public void Logout()
  {
  }
 }

对于 Test1 ,根据登录和注销两个状态,进行不同操作。

但是,对于 Test2,它只需要登录这个状态,其它情况不关它事。那么 Logout() 对他来说,完全没有用,这就是接口污染。

上面的代码就违法了接口隔离原则。

但是,接口隔离原则有个缺点,就是容易过多地将细分接口。一个项目中,出现成千上万个接口,将是维护地灾难。

因此接口隔离原则要灵活使用,就 Test2 来说,多继承一个方法无伤大碍,不用就是了。ASP.NET Core 中就存在很多这样的实现。


  public void Function()
  {
   throw new NotImplementedException();
  }

《设计模式之禅》第四章中,作者对接口隔离原则总结了四个要求:

1  接口尽量小:不出现臃肿(Fat)的接口。

2  接口要高内聚:提高接口、类、模块的处理能力。

3  定制服务:小粒度的接口可以组成大接口,灵活定制新的功能。

4  接口的设计有限度:难以有固定的标准去衡量接口的粒度是否合理。

另外还有关于单一职责原则和接口隔离原则的关系和对比。

单一职责原则是从服务提供者的角度去看,提供一个高内聚的、单一职责的功能;

接口隔离原则是从使用者角度去看,也是实现高内聚和低耦合。

接口隔离原则的粒度可能更小,通过多个接口灵活组成一个符合单一职责原则的类。

我们也看到了,单一职责原则更多是围绕类来讨论;接口隔离原则是对接口来讨论,即对抽象进行讨论。

开闭原则

开闭原则(Open/Closed Principle)规定 :

“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”

--《Object-Oriented Software Construction》作者 Bertrand Meyer
开闭原则意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。类的改动是通过增加代码实现,而不是修改源代码。

开闭原则 有 梅耶开闭原则、多态开闭原则。

梅耶开闭原则

​       代码一旦完成,一个类的实现只应该因错误而修改,新的或者改变的特性应该通过新建不同的类实现。

​       特点:继承,子类继承父类,拥有其所有的方法,并且拓展。

多态开闭原则

​       此原则使用接口而不是父类来允许不同的实现,您可以在不更改它们的代码的情况下轻松替换它们。

现在大多数情况下,开闭原则指的是多态开闭原则。

多态开闭原则笔者在查阅资料是,发现这个接口指的不是 Interface ,指的是抽象方法、虚方法。

问:面向对象的三大特性是什么?答:封装、继承、多态。

对,多态开闭原则就是指这个多态。不过,原则要求不应对方法进行重载(重写)、隐藏。

这是一个示例:


 // 实现登录注销
 public class UserLogin
 {
  public void Login() { }
  public void Logout() { }
  public virtual void A() {/* 做了一些事*

本文标题为:浅谈C#六大设计原则

基础教程推荐