在我的一些方法中,有Too Many Parameters并且很难维护和阅读 源代码。有时我担心“他们是否以适当的顺序传递适当的值?”
我使用Checkstyle作为我的Eclipse插件,并且警告 超过7个参数 。
我不确定它可能只是一个编码标准,我不关心它。但是当通过视图,服务或 dao 传递许多参数时,我注意到它很难阅读并且稍后难以修改次。
所以,我试图用......传递这些参数。
许多对象或 Bean 。但这给了我另一个问题,因为我的参数不能得到任何保证(不确定它们是否存在)。
HashMap 类型参数。但这可能会迫使我检查一些验证并尝试匹配方法调用方的 键 。
以上两种方法也可能会丢失编译时错误检查。 有没有减少参数计数的建议?
答案 0 :(得分:13)
传递HashMap是非类型化脚本语言中的常见做法,但在Java中却是一种不好的做法。它击败了强类型的优势,这是您在Java中获得生产力的一部分。换句话说,Java编译器无法帮助您在开发过程中发现错误,而且您更有可能在运行时捕获它们。
如果您传递的参数在概念上相关,您可以在提及时将它们分组到适当的对象中。例如,如果您传递诸如firstName,lastName,dateOfBirth等参数,则可以传递具有这些属性的Person对象。这使用OO使您的设计更容易思考和维护。
如果我理解你对此的意思:“但是这给了我另一个麻烦,因为我的参数不能得到任何保证(不确定是否包含)”,你可以强制执行保证当你的Person或者等对象被实例化时你需要。一种方法是使用不可变的Person(等)对象:没有setter,而是通过构造函数传递所有params。如果它们都不正确,则抛出IllegalArgumentException。
祝你好运!答案 1 :(得分:12)
有一些技术可以减少参数数量;
参考一些标准的java书籍;
还尝试学习设计模式,它作为最佳编码实践非常有用。
答案 2 :(得分:6)
在我提出我的建议之前,让我先谈谈所提出的建议。我冒昧地跳过“臭”解决方案--HashMap和bean,因为你可以清楚地看到它们的缺点。
盲目地使用辅助类来保存参数组。当团队凝聚力时(例如Mark Phillips的回答),他们可以真正发光,但是否则会导致问题(基本上就像打字的HashMap一样)。我怀疑它们是否适用于将7个参数从视图传递到DAO层的问题。
最小化的方法也很有意义,如果它们有意义的话(比如Effective Java book中的List示例)。我很少看到他们这样做的地方,所以我怀疑他们会解决你的问题。
Builder模式通常非常干净,但它只解决了一层的问题 - 它没有告诉你如何进一步传递参数。当你从视图中获得一个参数时,DAO中需要这个参数,那么构建器只会膨胀你的代码,无论如何你都需要传递参数。
在我最终提出解决方案之前,让我挑战一个常见的隐含假设,即所有数据都需要以方法参数的形式通过堆栈传递。只有将处理对象应用于应用程序或会话范围时才会出现这种情况。在请求处理开始时创建所有相关对象时,约束消失。然后,您可以使用对象的构造函数仅向其传递必要的信息。
在某种程度上,这类似于方法对象或命令模式。
为了应用此解决方案,您需要更改系统的入口点 - 通常这是某个视图层对象中的方法。让它负责两件事:
第一步至关重要。这是您从每个层构建请求范围对象的位置:视图,服务和DAO。对于每个对象,您只需将所需数据传递给其构造函数(例如,如果仅在DAO中需要参数“userIP” - 例如,用于审核DB访问,则仅将其传递给DAO请求对象)。请求对象还需要对其协作者的引用(如需要DAO的服务) - 相应地通过构造函数传递它们。
第二步:当您设置对象图时,只需在第一个上调用execute / run方法(通常是视图层中的对象)。
/** The example (in Scala) shows how your app's entry point could look like.
* The presented method belongs to an app-scoped view-layer object.
*/
def delete(itemId: Id, userIP: IPAddress) {
// Note, that only RepositoryHelperReq class is interested in the
// "itemId" and "userIP" parameters
val repoReq = MainRepositoryReq(RepositoryHelperReq(itemId, userIP))
val serviceReq = MainServiceReq(ServiceHelperReq(repoReq))
val viewReq = MainViewReq(ViewHelperReq(serviceReq))
viewReq.execute()
}
现在让我回应一些对这种模式的预期批评。
有人会说,性能会受到影响,因为堆上会有更多的对象来进行垃圾回收。 我会问那些测量,因为通常它不是对象分配,而是性能成本,而是对象保留(参见last presentation of Simon Ritter)。
有些人会询问应用程序或会话范围的数据,例如数据源或购物篮对象。这些对象仍然可以使用 - 您只需将它们注入到请求范围的对象中。
有些人会批评依赖结构,认为视图应仅依赖于服务而不依赖于DAO。这是有效的评论,只需注意,在经典的webapps中,你仍然有一个中心位置,这取决于所使用的每一层(通常被称为“世界末日”)。有时它是web.xml,有时它是Spring应用程序上下文或Guice模块。如果您关心正确的依赖关系,我建议您将所有工厂逻辑放在这样的位置,让它实现一些View-layer接口并注入视图。这样,您的整个依赖结构将保持干净和模块化。
有人会说,流行的DI框架(主要是Spring)支持这种模式非常糟糕。这是真的,你需要使用一个像样的DI库(Guice,Dagger for Java或Macwire,如果你喜欢Scala)或准备对抗Spring以做正确的事。
<强>积分强>
MiškoHevery在他出色的博客文章How to do Everything Wrong with Servlets中向我介绍了这一特定问题的解决方案,并在Managing Object Lifetimes中精确定位(参见“更常见的违规”片段)。我想感谢他的帖子,因为很难在其他来源找到关于这个具体问题的准确指导。
答案 3 :(得分:4)
第一种方法是要走的路,它本质上是封装(OO的基本原则之一)。至于&#34;但这给了我另一个麻烦,因为我的参数不能得到任何保证(不确定是否包含)。&#34; - 这是一个非常常见的问题,并且已经使用JSR 303解决了。这是一个带有JSR 303验证注释的bean的一个非常基本的例子:
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public class Book {
@NotNull
private String title;
@NotNull
private String author;
@Min(value=100)
private int numOfPages;
@NotNull
private String isbn;
... ... ...
}
以下是验证方法:
Book book = new Book();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Book>> violations = validator.validate(book);
for (ConstraintViolation<Book> violation : violations) {
System.out.format("%s: %s%n",violation.getPropertyPath(), violation.getMessage());
}
这是运行验证的输出:
isbn: may not be null
numOfPages: must be greater than or equal to 100
author: may not be null
title: may not be null
进一步阅读:http://www.jroller.com/eyallupu/entry/jsr_303_beans_validation_using
但是,您不必进行此类手动验证,例如在Spring MVC中,您可以将@javax.validation.Valid
放在传递给方法的bean上,它将自动验证。
答案 4 :(得分:2)
一般来说,我会说,尝试减少方法/类的责任以减少参数的数量。但如果真的需要它们,不要让插件阻止你。
答案 5 :(得分:2)
您必须能够通过以下选项解决此问题
1.构建者模式
2.伪命名参数方法将是一种很好的方法。(参考http://java.dzone.com/articles/named-parameters-java)(最适用于不可变对象)
3.命令模式。
只有在您使用方法中的所有7个参数时,上述选项才有效。 如果不是这个api的设计方式存在缺陷。
我在考虑设计方法和api时发现以下链接很有用,
http://www.infoq.com/presentations/effective-api-design
答案 6 :(得分:2)
通常会看到具有长参数列表的构造函数,特别是对于DAO。很多时候,我发现遵守类似的属性命名约定对于防止这种情况有很大帮助。
自从我编写了任何java代码以来已经有一段时间了,所以我需要传递一个java特定的例子。基本上,如果您具有相同名称和可分配类型的属性,则可以实现接受任何对象并将其映射到另一个对象的克隆函数。
在C#中,我在这里讨论过:Best way to clone properties of disparate objects
就类型安全而言,您可以控制命名约定。如果一个人总是意味着相同的东西,并且总是具有相同的类型,那么你将避免一些可能导致问题的常见陷阱。
如果我没有提到使用反射并不总是一个好方法,那将是我的疏忽。您需要考虑性能影响。另一个考虑因素是在持久化之前验证生成的对象。最后,要注意浅色副本。答案 7 :(得分:0)
我不知道你的包是如何分开的,而当传输数据包含太多参数时,我的建议是在javaee中创建一个调用 DTO (数据传输对象)的新Object。你说,这不容易保证。否则,使用 CALLBACK 是一种更容易保证和扩展的更好方法。没有什么是完美的,你可以选择哪个方面是你的注意力。
回调被视为
void function(Callback callback){
callback.do();
}
这是我的建议,但不是一个完全正确的方法。也许它对你有帮助。