小酌重构系列[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
拆分为了CustomerOrderService
和CustomerRegistrationService
。
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 } }
拆分后,我们可以看到:
- 这两个类的语义和它们的命名以及定义在其中的方法都是契合的。
- 类的粒度变小了,代码的可读性增强了,并且有利于将来的扩展、维护、修改。
在开发过程中,我们应该保持一个良好的习惯,为类中追加功能时,尽量确认好类的职责,并控制好类的粒度,这有益于代码的可读性、扩展性、维护和修改,这样就不会被上帝发现了。
本文链接: 文章作者:keepfool 文章出处:http://www.cnblogs.com/keepfool/ 如果您觉得阅读本文对您有帮助,请点一右下角的“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎看官们转载,转载之后请给出作者和原文连接。