你的位置:首页 > ASP.net教程

[ASP.net教程]小酌重构系列[12]去除上帝类


关于上帝类

神说:“要有光”,就有了光。——《圣经》。上帝要是会写程序,他写的类一定是“上帝类”。程序员不是上帝,不要妄想成为上帝,但程序员可以写出“上帝类”。上帝是唯一的,上帝的光芒照耀人间,上帝是很爱面子的,他知道程序员写了“上帝类”,抢了他的风头,于是他降下神罚要惩戒程序员。——既然你写了“上帝类”,那么就将你流放到艰难地修改和痛苦的维护的炼狱中,在地狱之火中永久地熬炼。

你看,上帝也是有脾气的,你做了什么他都知道,你不能抢他的风头,否则你就要付出代价,受到相应的惩罚。为息帝怒,咱们还是老老实实地编写一些“小类”吧。

有些开发者为了贪图简便,看到一个现成的类,也不管这个类是做什么的,需要追加功能时,就向这个类里面添加功能代码。久而久之,使得一些类变成了“上帝类”。什么是“上帝类”?上帝类也叫万能类,意指做了太多“事情”的类。在开发基于WebForms的应用程序时,Page页面的后置代码中包含了访问数据库、处理业务逻辑、绑定页面数据、页面事件处理等这些事情,这就是上帝类的一个举证(可能很多人都这么干过)。

上帝类的优缺点

优点

“存在即合理”——上帝类比较适用于一些较小的、稳定的应用开发场景,即那些业务逻辑不复杂、也不需要太多维护的应用程序。
比如:一些小工具的开发,不需要过多地考虑类的粒度和职责划分,楼主博客中用的Windows Live Writer代码高亮插件就是这么做的。

缺点

上帝类的缺点是显而易见的,上帝类的颗粒度较大,它缺乏可读性、可扩展性和可维护性。
上帝类违反了“SRP原则”,上帝类担任的职责太多了,该做的和不该做的它都做了。
同时也违反了“OCP原则”,上帝类功能之间的耦合性太高了,因此不具备可扩展性,当需求变化时,可能会涉及到大量代码的修改。

“SRP原则”和“OCP原则”的我就不再赘述了,想了解这两个原则,请参考该系列的另外两篇文章:分离职责和提取接口。

示例

重构前

下面这个CustomerService类,定义了5个方法:

  • CalculateOrderDiscount()方法:结合客户信息,计算订单的折扣
  • CustomerIsValid()方法:结合订单信息,判断客户是否有效
  • GatherOrderErrors()方法:结合订单的商品信息和客户信息,收集订单错误信息
  • Register()方法:注册客户信息
  • ForgotPassword()方法:处理客户忘记密码
public class CustomerService{  public decimal CalculateOrderDiscount(IEnumerable<Product> products, Customer customer)  {    // do work  }  public bool CustomerIsValid(Customer customer, Order order)  {    // do work  }  public IEnumerable<string> GatherOrderErrors(IEnumerable<Product> products, Customer customer)  {    // do work  }  public void Register(Customer customer)  {    // do work  }  public void ForgotPassword(Customer customer)  {    // do work  }}

在业务上,这些方法多少和Customer是有一些关联的。但这不意味着,只要是和Customer相关的方法都要放到CustomerService中。
这个类还可以在职责上做一些划分,粒度可以控制的在细一些。

重构后

重构后,我们按职责将CustomerService拆分为了CustomerOrderServiceCustomerRegistrationService

public class CustomerOrderService{  public decimal CalculateOrderDiscount(IEnumerable<Product> products, Customer customer)  {    // do work  }  public bool CustomerIsValid(Customer customer, Order order)  {    // do work  }  public IEnumerable<string> GatherOrderErrors(IEnumerable<Product> products, Customer customer)  {    // do work  }}public class CustomerRegistrationService{  public void Register(Customer customer)  {    // do work  }  public void ForgotPassword(Customer customer)  {    // do work  }}

拆分后,我们可以看到:

  • 这两个类的语义和它们的命名以及定义在其中的方法都是契合的。
  • 类的粒度变小了,代码的可读性增强了,并且有利于将来的扩展、维护、修改。

在开发过程中,我们应该保持一个良好的习惯,为类中追加功能时,尽量确认好类的职责,并控制好类的粒度,这有益于代码的可读性、扩展性、维护和修改,这样就不会被上帝发现了。