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

2022-09-03 17:13:07

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


我正在使用构建器模式在我们的系统中创建权限。我选择了构建器模式,因为安全性在我们的产品中非常重要(它涉及未成年人,因此COPPA等人),我认为权限必须可读,并且认为可读性至关重要(即使用流利风格的构建器模式而不是具有6个值的单个函数)。

代码如下所示:

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

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

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

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


答案 1

溶液

构建这种流畅的 API 模式的一个好方法是,不要只从每个方法返回,而是返回一个实例,该实例实现的 a 仅支持应位于列表中的方法,并让最后一个方法调用返回您需要的实际对象。thisMethod Object PatternInterfacenext

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

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


答案 2

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


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

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

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


推荐