我正在使用构建器模式在我们的系统中创建权限。我选择了一个构建器模式,因为安全性在我们的产品中非常重要(它涉及未成年人COPPA等),我认为权限必须是可读的,并且认为可读性至关重要(即使用一个流畅的样式构建器模式,而不是具有6个值的单个函数。)
代码如下所示:
permissionManager.grantUser( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() );
这些方法填充私有辅助bean,在使用终端方法(即asOf)时,将权限提交给数据库;如果那个方法没有被调用就没有任何反应。有时,开发人员会忘记调用终端方法,这种方法不会导致编译器错误,并且很容易错过快速阅读/略读代码。
我该怎么做才能防止这个问题?我不喜欢返回需要保存的Permission对象,因为这会引入更多噪音并使权限代码更难以阅读,跟踪,跟踪和理解。
我已经考虑过在终端命令标记的后台上放置一个标志。然后,检查finalize
方法中的标志,如果创建对象而没有持久化,则写入日志。 (我知道finalize
不能保证运行,但这是我能想到的最好的。)
答案 0 :(得分:11)
如果您真的想在代码中强制执行,可以为PMD或Findbugs编写规则。这样做的好处是它已经在编译时可用。
运行时: 如果您只想确保用户以正确的顺序调用构建器,则为每个步骤使用单独的接口。
grantUser()将返回具有方法permissionTo()的ISetPermission,该方法将返回一个IResourceSetter,其中包含方法item()...
您可以将所有这些接口添加到一个构建器,只需确保方法为下一步返回正确的接口。
答案 1 :(得分:11)
构建此流畅 API模式的好方法是,不是仅从每个方法返回this
,而是返回实现Method Object Pattern
的{{1}}实例只支持列表中应为Interface
的方法,并让最后一个方法调用返回您需要的实际对象。
如果这是获取该对象实例的唯一方法,则必须始终调用最后一个方法。
next
这会强制执行构造调用链,并且对代码完成非常友好,因为它显示了下一个接口是什么,它只有方法可用。
这为构造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);
}
}
}
对象提供了一种万无一失的无异常检查方法。
创建对象并存储它是不同的问题,不应混合使用。考虑到URL
并不意味着.build()
,反之亦然,.store()
指出关注点的混合立即在不同的地方做不同的事情,你得到了你想要的保证。
在另一个只接受完全构造的buildAndStore()
实例的方法中调用持久性代码。
答案 2 :(得分:6)
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<Has1,Has2,Has3> {
private String first;
private String second;
private String third;
private Builder() {}
public static Builder<False,False,False> create() {
return new Builder<>();
}
public Builder<True,Has2,Has3> setFirst(String first) {
this.first = first;
return (Builder<True,Has2,Has3>)this;
}
public Builder<Has1,True,Has3> setSecond(String second) {
this.second = second;
return (Builder<Has1,True,Has3>)this;
}
public Builder<Has1,Has2,True> setThird(String third) {
this.third = third;
return (Builder<Has1,Has2,True>)this;
}
}
public MyClass(Builder<True,True,True> 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"));
}
}
答案 3 :(得分:4)
现在有一个基于注释处理的编译器插件,如果不存在该方法,它将为您检查并抛出编译错误:Fluent API sentence end check。
您可以annotate your final method带有@End
批注,或者如果您不控制类,则仍可以provide a text file使用完全限定的方法名称,并进行检查
然后Maven can check during compilation。
它仅在Java 8以上版本中有效,因为它使用了在那里引入的新的编译器插件机制。
答案 4 :(得分:2)
步骤构建器模式可以完全满足您的需求:http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html
答案 5 :(得分:0)
在一个单独的步骤中应用新权限,该步骤首先验证构造函数是否正确构建:
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)
答案 6 :(得分:-1)
除了使用Diezel生成整个接口集外,还要强制他们获取“令牌”对象:
Grant.permissionTo( permissionManager.User( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() ) );
在last / exit方法返回正确的类型之前,用户将无法完成语句。 Grant.permissionTo可以是一个静态方法,静态导入,一个简单的构造函数。它将获得将权限实际注册到permissionManager所需的全部内容,因此无需配置或通过配置获取。
Guice的人们使用另一种模式。它们定义了一个“可调用的”,用于配置权限(在Guice中,所有这些都是关于绑定的。)
public class MyPermissions extends Permission{ public void configure(){ grantUser( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() ); } } permissionManager.add(new MyPermissions() );
grantUser是受保护的方法。 permissionManager可以确保MyPermissions仅包含完全限定的权限。
对于单一权限,这比第一个解决方案更糟糕,但是对于一堆许可来说它更清晰。