Java 如何确保构建器模式已完成?

Java 如何确保构建器模式已完成?,java,design-patterns,builder-pattern,Java,Design Patterns,Builder Pattern,编辑:我不担心被错误的顺序调用,因为这是通过使用多个接口实现的,我只是担心终端方法被调用 我正在使用生成器模式在系统中创建权限。我之所以选择构建器模式,是因为安全性在我们的产品中非常重要(它涉及到未成年人,所以等等),我觉得权限必须是可读的,并且觉得可读性是最重要的(即使用流畅样式的构建器模式,而不是使用一个具有6个值的单一函数) 代码如下所示: permissionManager.grantUser( userId ).permissionTo( Right.READ ).item( do

编辑:我不担心被错误的顺序调用,因为这是通过使用多个接口实现的,我只是担心终端方法被调用


我正在使用生成器模式在系统中创建权限。我之所以选择构建器模式,是因为安全性在我们的产品中非常重要(它涉及到未成年人,所以等等),我觉得权限必须是可读的,并且觉得可读性是最重要的(即使用流畅样式的构建器模式,而不是使用一个具有6个值的单一函数)

代码如下所示:

 permissionManager.grantUser( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() );
这些方法填充一个私有支持bean,该bean在拥有终端方法(即asOf)时向数据库提交权限;如果该方法未被调用,则不会发生任何事情。有时开发人员会忘记调用terminal方法,这不会导致编译器错误,并且在快速阅读/浏览代码时很容易出错

我可以做些什么来防止这个问题?我不希望返回需要保存的权限对象,因为这会引入更多的噪音,使权限代码更难阅读、跟踪和理解


我曾考虑过在由terminal命令标记的背衬上挂一个标志。然后,检查
finalize
方法中的标志,如果对象是在没有持久化的情况下创建的,则写入日志。(我知道,
finalize
不能保证运行,但这是我能想到的最好的了。)

如果您真的想在代码中强制执行,可以为PMD或Findbugs编写规则。这样做的好处是它在编译时已经可用


运行时: 如果您只想确保用户以正确的顺序调用生成器,那么请为每个步骤使用单独的界面

grantUser()将返回具有方法permissionTo()的ISetPermission,该方法将返回具有方法项()的IResourceSetter

您可以将所有这些接口添加到一个生成器中,只需确保这些方法为下一步返回正确的接口。

解决方案 构造这个fluent API模式的一个好方法是,不只是从每个方法返回
this
,返回一个
方法对象模式的实例
,该模式实现了一个
接口
,该接口只支持列表中应该是
下一个
的方法,并让最后一个方法调用返回您需要的实际对象

如果这是获取该对象实例的唯一方法,则必须始终调用最后一个方法

这加强了构造调用链,并且对代码完成非常友好,因为它显示了下一个接口是什么以及它唯一可用的方法

下面是一个更完整的示例,中间有一些可选内容:

这为构造
URL
对象提供了一种万无一失的无异常检查方法

将持久性与施工相结合是一个混合问题: 创建对象和存储对象是不同的关注点,不应混淆。考虑到
.build()
并不意味着
.store()
,反之亦然,
buildAndStore()
指出了关注点的混合,即立即在不同的地方做不同的事情,你就能得到你想要的保证


将对持久性代码的调用放在另一个方法中,该方法只接受完全构造的
权限实例

在单独的步骤中应用新权限,该步骤首先验证生成器是否正确构造:

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)
除了使用生成整个接口集之外,还可以强制它们获取“令牌”对象:

Grant.permissionTo(permissionManager.User(userId).permissionTo(Right.READ).item(docId).asOf(newdate()); 直到last/exit方法返回正确的类型,用户才能完成该语句。 Grant.permissionTo可以是静态方法、静态导入、简单构造函数。它将获得在permissionManager中实际注册权限所需的所有信息,因此不需要进行配置,也不需要通过配置获得

Guice的人们使用另一种模式。它们定义了一个“可调用的”,用于配置权限(在Guice中,全部是关于绑定的)

公共类MyPermissions扩展了权限{ public void configure(){ grantUser(userId).permissionTo(Right.READ).item(docId.asOf(newdate()); } } 添加(新的MyPermissions()); grantUser是一种受保护的方法。 permissionManager可以确保MyPermissions仅包含完全限定的权限


对于单个权限来说,这比第一个解决方案更糟糕,但是对于一组权限来说,它更干净。

有一个步骤生成器模式,它完全满足您的需要:

公共类MyClass{
私有最终字符串优先;
私人最后一串第二;
私人最后一串第三;
公共静态类False{}
公共静态类True{}
公共静态类生成器{
私有字符串优先;
私有字符串第二;
私人字符串第三;
私有生成器(){}
公共静态生成器create(){
返回新的生成器();
}
公共生成器setFirst(字符串优先){
this.first=first;
归还(建造商)此文件;
}
公共生成器设置秒(字符串秒){
这个秒=秒;
归还(建造商)此文件;
}
公共生成器设置第三(字符串第三){
这个。三分之一=三分之一;
归还(建造商)此文件;
}
}
公共MyClass(生成器){
first=builder.first;
秒=builder.second;
第三个=builder.third;
}
公共静态无效测试(){
//编译错误!
MyClass c1=新的MyClass(MyC
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)
Grant.permissionTo( permissionManager.User( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() ) ); public class MyPermissions extends Permission{ public void configure(){ grantUser( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() ); } } permissionManager.add(new MyPermissions() );
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"));
    }
}