参数对象究竟要不要?

首先,这篇文章只是我临时写的,之前没有给文章个整体思路,所以文章可能思路会显得有些乱,连我到现在都不知道我要写到什么时候终止。

 

这篇文章的产生是因为看过了横刀天笑的这篇文章,在这篇文章中,作者给出了一个很好的关于代码重构的步骤,以及我们要如何重构代码,提高抽象层次,但是我对其中一点,是对Martin Fowler在《重构》一书中所提出的关于参数对象的观点一直抱有质疑。先别急着反对,也别急着回复,先继续向下看。

1. 先想想Python , F#之类的函数式语言

让我们先想想Python , F#之类的函数式语言(让我们暂时先把Python称之为函数式语言吧),这些函数式语言,也包括C#(从4.0开始),都有一个概念叫做Tuple,我们看看Python如何来声明一个Tuple。

 

1 = ("kym",22,"developer")

 

不知道您看到这段代码是不是感到眼熟呢?我们来写一个方法,就是横刀天笑在文章中用到的一段近似的代码:

 

1 private bool Validate(string userName,int age,string email)
2 {
3     //....Validate
4     return true;
5 }

 

恩,那我们怎么来调用这个方法:

 

1 public static void Main(string[] args)
2 {
3     bool isValid = Validate("kym",22,"developer");
4     if(isValid)
5         Console.WriteLine("Success");
6     else
7         Console.WriteLine("Failure");
8 }

 

其实方法的参数调用是不是和Tuple很类似呢?想想我们把方法改成这样的形式:

1 def Validate(userInfo):
2     bool nameIsValid = userInfo[0]==''
3     // and so on 
4     return isValid

 

然后我们就可以把最之前的Tuple传入了:

 

1 if __name__ == '__main__':
2     print Validate(t)

 

相信这个时候,也许很多人知道我想表达什么概念了。

 

2. Tuple and Class

是的,其实我想表达的就是方法参数从本质上来说就一个Tuple而已,那么接下来让我们想想Tuple和Class的区别所在吧?

从语法分类上来讲,Tuple我们把他叫做序列,和List被划分到同一分类下。而Class即使在那些弱面向对象(让我们暂且这么叫吧),比如Javascript来说,也充其量与Dictionary , Hashtable划分到同一类下,完全不同。

一个Tuple,如果不考虑思维意义的话,我们也完全可以将之用List取代。

t = ('kym',22,'developer') 和 l = ['kym',22,'developer'] 在使用上是没有什么区别的。而同样,我们将之封装成一个User对象也无可厚非:

 

1 class User(Object):
2    def __init__(name,age,work):
3        self.name = name
4        self.age = age
5        self.work = work

 

但是从思维意义上来说,这几者是完全不同的:

其实在C#之类的强类型语言来说表现的更明显,List<T>只能用来用来容纳同一类型的元素,换句话说,List<T>只能用来容纳同一种意义的元素,我们可以称之为兄弟元素。

而Tuple和Class的区别则取决于元素之间的关系如何,让我们想想单一职责原则(SRP), 一个类下应该只有一个能够引起他变化的原因,也就是说,这些元素高内聚的关系,我们才能够把他们塞到一起。而Tuple相对来说,则是更弱的内聚关系。

 

3. 要不要重构

看完了上面的内容,那就让我们想想,究竟要不要重构。或者说要不要参数对象究竟取决于什么?

我们首先,不要把方法参数当成方法参数,而要把他们当成一个Tuple,也就是说我们把要不要重构的问题转换成了是Tuple还是Class的问题。

那么Tuple还是Class取决于什么,不是取决于参数的个数,而是完全取决于参数之间的内聚关系。

 

4. 一些小引申

相信很多人都见过这样的代码:

 

 1 public void Insert(string userName, string password, bool isValid)
 2 
 3     //是否需要验证
 4     if(isValid)
 5     { 
 6         bool userNameValid = userName != '';
 7         bool passwordValid = IsStrongPassword(password);
 8         if(userNameValid && passwordValid)
 9         {
10             this.StoreIntoDatabase(username,password);
11         }
12     }
13     else
14     {
15          this.StoreIntoDatabase(username,password);
16     }
17 }

 

先说这个参数是不是很长,如果你觉得还不够长,那么就把什么Address,Job之类通通塞进去吧!够长了吧!可是我们是不是应该把这些参数封装成一个参数对象?如果是?那么这个类要怎么写?

 

1 class UserArgsForValidation
2 {
3     string userName;
4     string password;
5     bool isValid;
6 }

 

这样真的就面向对象了么?明显不是,可是这个问题到底出在哪?是方法重载的过分滥用。

如果我没有记错,Martin Fowler在书中提到过这一点,如果在方法中出现了对参数的if判断或者switch等条件选择语句,那么就让我们把这个方法拆成InsertWithValid(string userName , string password)和InsertWithoutValid(string userName, string password)这两个方法吧。

所以,在大多数情况下,如果一个方法带有了很多的参数,其实在一般的意义上,这些参数一定是有一个相对高的内聚性,如果没有,诸如上面的情况,就先想想是不是该用用其他的重构原则,如果没有,那么就想想是Tuple还是Class,而不是这个参数有四个,还有八个。

其实有个很好的区分原则,记得在设计模式的圈子有这样一个说法,说如果你设计的类名带有了这个模式的名称,比如ProductFactory, AccessAdapter,那么你就是设计模式滥用了,当然这个说法有些极端。但是我们不妨也试试把这个原则迁移到方法重构上,如果你的类名上带有了Args这样的参数,那么你也是重构的滥用了。

posted @ 2011-04-26 11:18  飞林沙  阅读(3450)  评论(25编辑  收藏  举报