如何确保构建器模式完成?
编辑:我不担心被错误的顺序调用,因为这是通过使用多个接口强制执行,我只是担心终端方法被调用。
我正在使用构建器模式在我们的系统中创建权限。 我选择了一个构建器模式,因为安全性在我们的产品中非常重要(它涉及COPPA等人的未成年人),我认为权限必须可读,并且认为可读性至关重要(即使用流畅的风格构建器模式而不是具有6个值的单个函数)。
代码如下所示:
permissionManager.grantUser( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() );
这些方法填充私有辅助bean,在终端方法(即asOf)提交数据库权限时; 如果那个方法没有被调用就没有任何反应。 有时,开发人员会忘记调用终端方法,这种方法不会导致编译器错误,并且很容易错过快速读取/浏览代码。
我该怎么做才能防止这个问题? 我不想返回需要保存的Permission对象,因为这会引入更多噪音并使权限代码更难以阅读,跟踪,跟踪和理解。
我曾想过在后台上放置一个标志,该标志由终端命令标记。 然后,检查finalize
方法中的标志,如果创建对象而没有持久化,则写入日志。 (我知道finalize
无法保证运行,但这是我能想到的最好的。)
现在有一个基于注释处理的编译器插件,如果方法不存在,将为您检查,并抛出编译错误: Fluent API句子结束检查 。
您可以使用@End
注释注释最终方法 ,或者如果您不控制该类,您仍然可以提供具有完全限定方法名称的文本文件 ,并使检查工作。
然后Maven可以在编译期间进行检查 。
它只能在Java 8中向上运行,因为它使用了新的编译器插件机制。
如果您真的想在代码中强制执行它,可以为PMD或Findbugs编写规则。 这将具有在编译时已经可用的优点。
运行时:如果您只想确保用户以正确的顺序调用构建器,则为每个步骤使用单独的接口。
grantUser()将返回具有方法permissionTo()的ISetPermission,该方法将返回具有方法item()的IResourceSetter …
您可以将所有这些接口添加到一个构建器,只需确保方法为下一步返回正确的接口。
解
构造这种流畅的 API模式的好方法不是从每个方法返回this
,而是返回一个Method Object Pattern
的实例,该Method Object Pattern
实现一个Interface
,该Interface
只支持列表中应该是next
一个方法并且最后一个方法调用返回你需要的实际对象。
如果这是获取该对象实例的唯一方法,则始终必须调用最后一个方法。
Q6613429.java
package com.stackoverflow; import javax.annotation.Nonnull; import java.util.Date; public class Q6613429 { public static void main(final String[] args) { final Rights r = PermissionManager.grantUser("me").permissionTo("ALL").item("EVERYTHING").asOf(new Date()); PermissionManager.apply(r); } public static class Rights { private String user; private String permission; private String item; private Date ofDate; private Rights() { /* intentionally blank */ } } public static class PermissionManager { public static PermissionManager.AssignPermission grantUser(@Nonnull final String user) { final Rights r = new Rights(); return new AssignPermission() { @Override public AssignItem permissionTo(@Nonnull String p) { r.permission = p; return new AssignItem() { @Override public SetDate item(String i) { r.item = i; return new SetDate() { @Override public Rights asOf(Date d) { r.ofDate = d; return r; } };} };} }; } public static void apply(@Nonnull final Rights r) { /* do the persistence here */ } public interface AssignPermission { public AssignItem permissionTo(@Nonnull final String p); } public interface AssignItem { public SetDate item(String i); } public interface SetDate { public Rights asOf(Date d); } } }
这会强制构造调用链,并且对代码完成非常友好,因为它显示了下一个接口是什么,它只有方法可用。
这是一个更完整的例子,中间有可选的东西:
UrlBuilder.java
这提供了一种简单的检查无exception构造URL
对象的方法。
混合持久性与结构混合是一个问题:
创建对象并存储它是不同的问题,不应混合使用。 考虑到.build()
并不意味着buildAndStore()
反之亦然,而buildAndStore()
指出关注的混合立即在不同的地方做不同的事情,你得到你想要的保证。
在另一个只接受完全构造的Rights
实例的方法中调用持久性代码。
public class MyClass { private final String first; private final String second; private final String third; public static class False {} public static class True {} public static class Builder { private String first; private String second; private String third; private Builder() {} public static Builder create() { return new Builder<>(); } public Builder setFirst(String first) { this.first = first; return (Builder)this; } public Builder setSecond(String second) { this.second = second; return (Builder)this; } public Builder setThird(String third) { this.third = third; return (Builder)this; } } public MyClass(Builder builder) { first = builder.first; second = builder.second; third = builder.third; } public static void test() { // Compile Error! MyClass c1 = new MyClass(MyClass.Builder.create().setFirst("1").setSecond("2")); // Compile Error! MyClass c2 = new MyClass(MyClass.Builder.create().setFirst("1").setThird("3")); // Works!, all params supplied. MyClass c3 = new MyClass(MyClass.Builder.create().setFirst("1").setSecond("2").setThird("3")); } }
步骤构建器模式可以完全满足您的需求: http : //rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html
在单独的步骤中应用新权限,该步骤首先validation构造器是否正确构造:
PermissionBuilder builder = permissionManager.grantUser( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() ); permissionManager.applyPermission(builder); // validates the PermissionBuilder (ie, was asOf actually called...whatever other business rules)
除了使用Diezel生成整个接口集之外,还要强制它们获取“令牌”对象:
Grant.permissionTo(permissionManager.User(userId).permissionTo(Right.READ).item(docId).asOf(new Date()));
在last / exit方法返回正确的类型之前,用户将无法完成语句。 Grant.permissionTo可以是一个静态方法,静态导入,一个简单的构造函数。 它将获得将权限实际注册到permissionManager所需的全部内容,因此无需配置或通过配置获取。
Guice的人们使用另一种模式。 它们定义了一个“可调用的”,用于配置权限(在Guice中,所有这些都是关于绑定的)。
公共类MyPermissions扩展Permission { public void configure(){ grantUser(userId).permissionTo(Right.READ).item(docId).asOf(new Date()); } } permissionManager.add(new MyPermissions());
grantUser是受保护的方法。 permissionManager可以确保MyPermissions仅包含完全限定的权限。
对于单一权限,这比第一种解决方案更糟糕,但是对于一堆许可来说它更清晰。
- 如何使用Ant为具有外部jar依赖性的java项目构建可分发jar
- java.io.IOException:服务器返回HTTP响应代码:503为URL:http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
- 等待后台进程以不同类的SwingWorker结束
- ExecutorService在调用线程中运行任务?
- 如何在Selenium WebDriver中使用不同版本的IE(IE6,7,8,9和10)和Java
- 使用Python或Java自动化HP Quality Center
- 是否有StringUtils.isNumeric的替代方法,我的意思是什么?
- 在JPA / EclipseLink中保留包含订单和重复项的列表
- sparkContext JavaSparkContext SQLContext SparkSession之间的区别?