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

发布于 2024-11-18 23:09:02 字数 614 浏览 7 评论 0原文

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


我正在使用构建器模式在我们的系统中创建权限。我选择了构建器模式,因为安全性在我们的产品中非常重要(它涉及未成年人,因此

代码如下所示:

 permissionManager.grantUser( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() );

这些方法填充一个私有支持 bean,当终端方法(即 asOf )将权限提交到数据库时;如果该方法没有被调用,则什么也不会发生。有时,开发人员会忘记调用终端方法,这不会导致编译器错误,并且在快速阅读/浏览代码时很容易错过。

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

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

EDIT: I am not worried about being called in the wrong order since this is enforced through using multiple interfaces, I am just worried about the terminal method getting called at all.


I am using a builder pattern to create permissions in our system. I chose a builder pattern because security is so important in our product (it involves minors so COPPA et al), I felt it was imperative that permissions be readable, and felt that the readability was of the utmost importance (i.e. use a fluent-style builder pattern rather than a single function with 6 values).

The code looks like this:

 permissionManager.grantUser( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() );

The methods populate a private backing bean, that upon having the terminal method (i.e. asOf ) commit the permission to the database; if that method does not get called nothing happens. Occasionally developers will forget to call the terminal method, which does not cause a compiler error and is easy to miss on a quick reading/skimming of the code.

What could I do to prevent this problem? I would not like to return a Permission object that needs to get saved since that introduces more noise and makes permission code harder to read, follow, track, and understand.

I have thought about putting a flag on the backing which gets marked by the terminal command. Then, check the flag in the finalize method and write to the log if the object was created without persisting. (I know that finalize is not guaranteed to run, but it's the best I can think of.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

季末如歌 2024-11-25 23:09:02

解决方案

构建这种流畅 API 模式的一个好方法是,返回一个方法对象模式的实例,而不是仅仅从每个方法返回this实现一个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

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

将持久性与构造混合在一起就是混合关注点:

创建对象和存储对象是不同的关注点,不应混合。考虑到 .build() 并不意味着 .store() ,反之亦然,并且 buildAndStore() 立即指出关注点的混合不同的地方有不同的东西,你会得到你想要的保证。

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

Solution

A good way to structure this fluent API pattern is instead of just returning this from each method, return an instance of a Method Object Pattern that implements an Interface that only supports the method that should be next in the list and have the last method call return the actual object you need.

If that is the only way to get an instance of that object, the last method will always have to be called.

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);
        }
    }
}

This enforces the chain of construction calls, and is very friendly with code completion as it shows what the next interface is and it only method available.

Here is a more complete example with optional things in the middle:

UrlBuilder.java

This provides a foolproof checked exception free way to construct URL objects.

Mixing the persistence with the construction is mixing concerns:

Creating the object and storing it are different concerns and should not be mixed. Considering that .build() does not imply .store() and vice-versa and buildAndStore() points out the mixing of concerns immediately do the different things in different places and you get the guarantees you want.

Put your call to your persistence code in another method that only accepts a fully constructed instance of Rights.

一梦浮鱼 2024-11-25 23:09:02

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


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

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

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

You could write a rule for PMD or Findbugs if you really want to enforce it in the code. This would have the advantage that it is already available at compile time.


Runtime:
If you only want to make sure the users call your builder in the correct order then use separate interfaces for each step.

grantUser() will return ISetPermission which has the method permissionTo(), which will return an IResourceSetter which has the method item()...

You can add all those interfaces to one builder, just make sure that the methods return the correct interface for the next step.

离不开的别离 2024-11-25 23:09:02

现在有一个基于注释处理的编译器插件,它将为您检查这一点,并在该方法不存在时抛出编译错误: Fluent API 句子结尾检查

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

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

它仅适用于 Java 8 及以上版本,因为它使用了其中引入的新编译器插件机制。

There is now an annotation processing based compiler plugin, that will check that for you, and throw compilation error, if the method is not present: Fluent API sentence end check.

You can either annotate your final method with @End annotation, or if you do not control the class, you can still provide a text file with fully qualified method name(s), and get the check working.

Then Maven can check during compilation.

It only works in Java 8, upwards, because it uses new compiler plugin mechanism, introduced there.

秋日私语 2024-11-25 23:09:02
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"));
    }
}
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"));
    }
}
电影里的梦 2024-11-25 23:09:02

有一个步骤构建器模式可以完全满足您的需要: http: //rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html

There is the step builder pattern that does exactly what you needed : http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html

凉薄对峙 2024-11-25 23:09:02

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

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)

Apply the new permission in a separate step that first validates that the Builder was constructed correctly:

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)
变身佩奇 2024-11-25 23:09:02

除了使用 Diezel 生成整套接口之外,就是强制他们获取“token” “对象:

    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 仅包含完全限定的权限。

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

Apart from using Diezel to generate the whole set of interfaces, is to force them getting the "token" object:

    Grant.permissionTo( permissionManager.User( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() ) );

users won't be able to finish the statement until the last/exit method returns the right type.
The Grant.permissionTo can be a static method, statically imported, a simple constructor. It will get all it needs to actually register the permission into the permissionManager, so it doesn't need to be configured, or obtained via configuration.

Folks at Guice uses another pattern. They define a "callable", that is used to configure permission (in Guice it's all about binding instead).

    public class MyPermissions extends Permission{

    public void configure(){
    grantUser( userId ).permissionTo( Right.READ ).item( docId ).asOf( new Date() );
    }

    }

    permissionManager.add(new MyPermissions() );

grantUser is a protected method.
permissionManager can ensure that MyPermissions only contains fully qualified permissions.

For a single permission this is worst than the first solution, but for a bunch of permission it's cleaner.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文