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

Sle*_*led 13 java design-patterns builder-pattern

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


我正在使用构建器模式在我们的系统中创建权限.我选择了一个生成器模式,因为安全是我们的产品很重要(涉及未成年人这么COPPA等),我觉得这是迫切需要的权限是可读的,并认为可读性是极为重要的(即使用一个流畅的风格构建器模式而不是具有6个值的单个函数).

代码如下所示:

 permissionManager.grantUser( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() );
Run Code Online (Sandbox Code Playgroud)

这些方法填充私有辅助bean,在终端方法(即asOf)提交数据库权限时; 如果那个方法没有被调用就没有任何反应.有时,开发人员会忘记调用终端方法,该方法不会导致编译器错误,并且很容易错过快速阅读/略读代码.

我该怎么做才能防止这个问题?我不想返回需要保存的Permission对象,因为这会引入更多噪音并使权限代码更难以阅读,跟踪,跟踪和理解.

我已经考虑过在终端命令标记的后台上放置一个标志.然后,检查finalize方法中的标志,如果创建对象而没有持久化,则写入日志.(我知道finalize不能保证运行,但这是我能想到的最好的.)

Leo*_*ngs 11

如果您真的想在代码中强制执行它,可以为PMD或Findbugs编写规则.这将具有在编译时已经可用的优点.


运行时:如果您只想确保用户以正确的顺序调用构建器,则为每个步骤使用单独的接口.

grantUser()将返回具有方法permissionTo()的ISetPermission,该方法将返回一个具有方法item()的IResourceSetter ...

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


小智 11

构造这种流畅的 API模式的一种好方法是,不是this从每个方法返回,而是返回一个Method Object Pattern实现的实例,该实例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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这会强制构造调用链,并且对代码完成非常友好,因为它显示了下一个接口是什么,它只有方法可用.

这是一个更完整的例子,中间有可选的东西:

UrlBuilder.java

这提供了一种简单的检查无异常构造URL对象的方法.

混合持久性与结构混合是一个问题:

创建对象并存储它是不同的问题,不应混合使用.考虑到这.build()并不意味着.store(),反之亦然,并buildAndStore()指出关注的混合立即在不同的地方做不同的事情,你得到你想要的保证.

在另一个只接受完全构造的实例的方法中调用持久性代码Rights.


Ond*_*her 7

现在有一个基于注释处理的编译器插件,它会为你检查,如果方法不存在,则会抛出编译错误:Fluent API sentence end check

您可以使用注释对最终方法进行@End注释,或者如果您不控制类,您仍然可以提供具有完全限定方法名称的文本文件,并使检查工作。

然后Maven 可以在编译期间检查

它只适用于 Java 8 以上,因为它使用了新的编译器插件机制,在那里引入。


cli*_*nux 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"));
    }
}
Run Code Online (Sandbox Code Playgroud)