参数对象究竟要不要?
飞林沙
首先,这篇文章只是我临时写的,之前没有给文章个整体思路,所以文章可能思路会显得有些乱,连我到现在都不知道我要写到什么时候终止。
这篇文章的产生是因为看过了横刀天笑的这篇文章,在这篇文章中,作者给出了一个很好的关于代码重构的步骤,以及我们要如何重构代码,提高抽象层次,但是我对其中一点,是对Martin Fowler在《重构》一书中所提出的关于参数对象的观点一直抱有质疑。先别急着反对,也别急着回复,先继续向下看。
1. 先想想Python , F#之类的函数式语言
让我们先想想Python , F#之类的函数式语言(让我们暂时先把Python称之为函数式语言吧),这些函数式语言,也包括C#(从4.0开始),都有一个概念叫做Tuple,我们看看Python如何来声明一个Tuple。
1 t = ("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这样的参数,那么你也是重构的滥用了
补充:综合编程 , 其他综合 ,