Java方法参数太多怎么办—Part6—方法返回值,javapart6
Java方法参数太多怎么办—Part6—方法返回值,javapart6
目录
- 自定义类型
- 引入参数对象
- Builder模式
- 重载
- 方法命名
- 方法返回值
本文是这个系列的第六篇文章,介绍了通过方法返回值应对参数过多的问题。如果你也希望参与类似的系列文章翻译,可以加入我们的Java开发 和 技术翻译 小组。
在前面文章中,讨论了如何直接减少构造函数和方法的参数,比如通过自定义类型、引入参数对象、Builder模式、重载和方法命名来减少参数。你可能会奇怪为什么会讨论方法返回值。然而,实际开发中开发者会使用参数作为返回值,从而增加了额外的参数。
非构造函数一般会在方法签名中指定返回值,声明返回值类型。然而,一个令人困扰的问题是:这种方法只支持一个返回值…
Java异常处理机制提供了另一种结果返回方式。检查出的异常会通过throws语句通知调用者。正如Jim Waldo在他的书“Java:The Good Parts”中写道:“将Java异常看作一种特殊的方法返回——只提供Throwable类型的返回值,这样更容易理解。”
“向方法传入可改变参数,然后在方法中改变参数状态”,这种方法可以让参数作为返回值。方法执行以后,参数对象就包含了被方法改变的内容。调用者通过改变后的参数能获得方法设定的最新状态。虽然任何可变的参数对象都可以做到这一点,但是对于那些想通过方法参数传递返回值的开发者来说格外有吸引力。
但是,这种做法有一个缺点——它破坏了“最少惊讶原则”。大多数开发者都希望方法参数仅作为输入而不是输出(Java不提供语法区分两者的不同)。Bob Martin在他的”Clean Code“中写道:“总的来看,应该避免使用输出参数。”另外,这种做法还使传入的可变参数更加混乱。考虑到这些情况,接下来本文将讨论更好的办法支持多个返回值。
译注:最小惊讶原则(principle of least astonishment),通常是用在用户界面方面,但同样适用于编写的代码。指的是代码应该尽可能减少让读者感到意外。
虽然Java中方法的返回值只能是一个单独的对象或者基本类型,但是考虑到对象是可以由我们自行决定,因而不会对我们造成限制。有一些做法我并不推荐。其中一种做法是,返回一个Object对象集合或数组,而其中包含了毫不相关的Object对象。例如,方法返回一个包含了三个值的数组或集合。这种做法的一个变种是,用一个二元组或N元组返回许多的相关值。另一个变种是,返回一个Map对象;Map键值可以随意设定以便关联相关的值。和其它方法一样,这样会给客户带来不必要的负担——因为他们必须知道键值的含义,然后才能通过键值获取他们想要的对象。
下面的示例代码展示了在不破坏方法参数的前提下提供多个返回值。这不是一个很好的做法:
通过多个通用数据结构提供多个返回
// =============================================================== // 注意:下面的示例只是为了说明文中的观点,不建议在生产代码中使用。 // =============================================================== /** * 获取电影信息。 * * @return 电影信息数组。信息的内容依据索引号进行分类: * 0 : 电影标题 * 1 : 发布时间 * 2 : 导演 * 3 : 分级 */ public Object[] getMovieInformation() { final Object[] movieDetails = {"World War Z", 2013, "Marc Forster", "PG-13"}; return movieDetails; } /** * 获取电影信息。 * * @return 电影信息列表。信息的内容依据信息在列表中的顺序分类; * 顺序分别是:电影标题、发布时间、导演、分级。 */ public List<Object> getMovieDetails() { return Arrays.<Object>asList("Ender's Game", 2013, "Gavin Hood", "PG-13"); } /** * 获取电影信息。 * * @return 电影信息Map。可以通过键值查找电影信息; * 支持的键值分别是:"Title"、"Year"、"Director"和"Rating"。 */ public Map<String, Object> getMovieDetailsMap() { final HashMap<String, Object> map = new HashMap(); map.put("Title", "Despicable Me 2"); map.put("Year", 2013); map.put("Director", "Pierre Coffin and Chris Renaud"); map.put("Rating", "PG"); return map; }
示例代码虽然不通过方法参数提供返回值,但是给调用者造成了一些不必要的负担——调用者必须对返回的数据结构细节有详细的了解。这些做法能够减少方法参数,并且不违反“最少惊讶原则”。然而,要求客户了解复杂数据结构的细节不是一种好的做法。
我更喜欢通过自定义类提供多个返回值。相比使用数组、集合或元组结构,这样会增加一些工作量。但这一点点的工作量(使用IDE完成这项工作只需要几分钟)却可以大大提高代码的可读性和流畅性。这是其它方法无法做到的:不用编写Java doc文档解释;也不需要调用者认真阅读我的代码,了解数组和集合中提供的值是什么、顺序如何、元组中的值是什么。自定义类提供的方法可以精确地描述它们提供了怎样的值。
下面的示例代码展示了一个由NetBeans编写的Movie类。当方法返回该Movie实例时,可以将Movie作为方法的返回类型,而不用使用可读性较差的通用数据结构。
Movie.java
package dustin.examples; import java.util.Objects; /** * Simple Movie类展示了怎样在提供多个返回值的时候兼具良好的可读性。 * * @author Dustin */ public class Movie { private final String movieTitle; private final int yearReleased; private final String movieDirectorName; private final String movieRating; public Movie(String movieTitle, int yearReleased, String movieDirectorName, String movieRating) { this.movieTitle = movieTitle; this.yearReleased = yearReleased; this.movieDirectorName = movieDirectorName; this.movieRating = movieRating; } public String getMovieTitle() { return movieTitle; } public int getYearReleased() { return yearReleased; } public String getMovieDirectorName() { return movieDirectorName; } public String getMovieRating() { return movieRating; } @Override public int hashCode() { int hash = 3; hash = 89 * hash + Objects.hashCode(this.movieTitle); hash = 89 * hash + this.yearReleased; hash = 89 * hash + Objects.hashCode(this.movieDirectorName); hash = 89 * hash + Objects.hashCode(this.movieRating); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Movie other = (Movie) obj; if (!Objects.equals(this.movieTitle, other.movieTitle)) { return false; } if (this.yearReleased != other.yearReleased) { return false; } if (!Objects.equals(this.movieDirectorName, other.movieDirectorName)) { return false; } if (!Objects.equals(this.movieRating, other.movieRating)) { return false; } return true; } @Override public String toString() { return "Movie{" + "movieTitle=" + movieTitle + ", yearReleased=" + yearReleased + ", movieDirectorName=" + movieDirectorName + ", movieRating=" + movieRating + '}'; } }
用单个对象提供多个返回值
/** * Provide movie information. * * @return Movie information. */ publicMovie getMovieInfo() { returnnewMovie("Oblivion",2013,"Joseph Kosinski","PG-13"); }
通过NetBeans类创建向导选择类名和包,然后键入了类的四个属性。然后,我使用NetBeans的“插入代码”功能插入get方法,同时重载了toString()、hashCode()和equals(Object)方法。如果觉得不需要这些,我可以让类变得更简单。整个创建过程非常简单,只花费了我5分钟。现在我有了一个更有用的返回类型,这一点可以在使用Movice类的代码中看到:无需多余的Java doc文档对返回类型进行说明,因为Movice类具有自描述性——它的get方法会告诉调用者返回的信息。我感觉与使用参数返回值或通用数据类型相比,为返回值创建类是值得的。这样做会给你带来巨大的回报。
使用自定义类包装多个返回值是一种很有吸引力的解决方案,这并不令人吃惊。毕竟从概念上来讲,它和我之前文章中提到的“使用自定义类和参数对象给方法传递相关参数,而不是将它们分别传递“是相似的。Java是一门面向对象的语言,当看到在代码中不使用对象来组织方法参数和返回值时,我会感到很吃惊。
好处和优点
使用自定义对象封装多个返回值的优点非常明显:方法参数仅仅作为“输入”,所有的输出信息(除了通过异常输出的错误信息) 都包含在方法返回的自定义类型实例中。相比使用数组、集合、Map、元组或者其他的通用数据结构,这是一种更加简洁的方法;前者将开发的工作转移给了方法的调用者。
代价和缺点
我没有发现使用自定义对象封装多个返回值有什么缺点。也许最明显的代价就是需要编写和测试这些类,但这个代价实际上非常小——这些类大都是非常简单,而且IDE会为我们完成大部分工作;IDE会自动完成,而且产生的代码没有错误;这些类非常简单,代码审查人员阅读和测试起来都非常容易。
如果继续寻找其它的代价和缺点,有人可能会说“这些类会让代码变得臃肿”。我不认为这是一个有力的反驳。尽管存在”编写的类很糟糕“这种风险,但是我认为调用者对一般类型参数错误地理解更有可能发生。另外一个可能的风险是,开发者将许多不相关的数据放入同一个类,而这些数据与方法的多个返回值之间没有太大的关系。既便如此,我发现唯一的好办法是修改代码使其不需要返回多个值。在自定义类的对象中包含许多不相关的数据,比起一般类型的参数提供返回值还是要好。事实上,当包含的值与值之间相关性越来越弱时,一般类型的数据结构会变得越来越难用。
结论
自定义参数对象类型帮助我们直接地解决了Java方法中参数过多的问题。幸运的是,这些自定义类型和参数对象还间接地减少了需要的参数个数。因为通过自定义类型可以从方法中返回多个值,无需额外增加参数用作返回值。
原文链接: dzone 翻译: Wld5.com - 李文举译文链接: http://www.wld5.com/6905.html
[ 转载请保留原文出处、译者和译文链接。]
用户点评