在 Java 中使用 Consumers 作为 setter 和 Suppliers 作为 getter 是不好的做法吗?

Is it bad practice to use Consumers as setters and Suppliers as getters in Java?

我有一个 Java class,它有一些我不打算为其创建 setter 和 getter 的私有变量;我希望这些变量保持不可访问。但是有一个 class 需要访问这些变量。这个 class 是不同包中的访问者(我更愿意将它保存在不同的包中)。允许此 class 向访问者提供充当设置器和获取器的消费者和供应商,以便访问者可以读取和修改这些变量,这是不好的做法吗?如果是,请说明原因。

示例:

A.java

public class A {
    private int x;
    private Consumer<Integer> setter;
    private Supplier<Integer> getter;
    public A(int v) {
        x = v;
        setter = new Consumer<Integer>() {
            @Override
            public void accept(Integer t) {
                x = t;
            }
        };

        getter = new Supplier<Integer>() {
            @Override
            public Integer get() {
                return x;
            }
        };
    }

    public void accept(SomeVisitor visitor) {
        visitor.setSetter(setter);
        visitor.setGetter(getter);
        visitor.visit(this);
    }
}

SomeVisitor.java

public class SomeVisitor extends ParentVisitor {
    private Consumer<Integer> setter;
    private Supplier<Integer> getter;

    public SomeVisitor() {
        setter = null;
        getter = null;
    }

    public void setSetter(Consumer<Integer> setter) {
        this.setter = setter;
    }

    public void setGetter(Supplier<Integer> getter) {
        this.getter = getter;
    }

    @Override
    public void visit(A a) {
        // Code that will, possibly, read and modify A.x
        ...
    }
}

这样变量 A.x 除了访问者之外的每个 class 都无法访问。


更多详情:

我有一些 class 会利用访问者。这些 classes 具有相互依赖的私有变量。如果这些变量有设置器,当用户更改这些变量时可能会出现不一致,这应该相互依赖,而不尊重这些依赖关系。

其中一些变量会有 getter,而另一些则没有,因为它们只会在内部使用,不应在其他地方访问。访问者是一个例外并且应该 read/write 访问这些变量的原因是访问者打算实现的功能应该在这些 classes 的方法中实现。但我认为如果我使用访问者会更干净。这些功能确实需要 read/write 访问这些变量。

此方法背后的意图是模拟 C++ 中的友元功能。我可以将访问者放在与这些 classes 相同的包中(如果我找不到解决此问题的巧妙方法,我会这样做);但是我觉得如果还有访客的话包裹会看起来很乱(而且访客会很多)。


访问者将实现的功能也与这些class彼此之间的关系有关。

作为 getter 和 setter are evil anyway,你最好不要让事情比普通的 getter 和 setter 更复杂。

我试图把它挤进评论里,因为它在技术上没有回答关于这是否是一个 "Bad Practice™" 的问题,但这个术语很难定义,因此,它几乎不可能给出答案......

这似乎最终归结为如何 Make java methods visible to only specific classes 的问题(还有类似的问题)。 getter/setter 应该只对一个特定的 class - 即访问者可用。

您在问题中使用了非常通用的名称和描述,很难说这在一般情况下是否有意义。

但有几点需要考虑:

  • 有人可能会争辩说这通常会破坏封装。每个人都可以编写这样的访问者并获得对 get/set 方法的访问权。即使这将是一个荒谬的 hack:如果人们想要实现一个目标,他们会做那样的事情!(在下面的附录 1 中绘制)

  • 更一般地说,有人可能会争辩说:为什么只允许访问者访问setter/getter,而其他classes不是?

  • 将 getter/setter 方法隐藏在 Supplier/Consumer 实例后面的一个令人信服的理由 可能 与可见性和classes 的特异性(详见附录 2)。但是由于访问者总是对被访问的 class 有依赖性,所以这里不能直接应用。

  • 有人可能会争辩说这种方法更容易出错。想象一下 setter 或 getter 是 null 或者它们属于不同实例的情况。调试它可能非常困难。

  • 如评论和其他答案所示:有人可能会争辩说,提议的方法只会使事情复杂化,"hides" 事实上,这些实际上是 setter/getter 方法。我不会说拥有 setter/getter 方法一般来说已经是一个问题了。但是您现在的方法是在访问者中设置 setter-setters 和 getter-setters。这以一种难以理解的方式扩展了访问者的 状态 space

总结一下:

尽管有上面提到的论点,但我不会将其称为 "bad practice" - 还因为它根本不是一个常见的 实践 ,而是一个非常具体的解决方法.这样做可能有理由和论据,但只要您不提供更多细节,就很难说这在 您的特定案例 中是否正确,或者是否有更多优雅的解决方案。


更新

关于添加的详细信息:你说过

inconsistencies could arise as users change these variables

通常 class 有责任管理自己的状态 space,确保其始终处于 "consistent"。而且,从某种意义上说,这是首先拥有 classes 和封装的主要 目的。为什么 getters+setters 有时被认为是 "evil" 的原因之一不仅仅是可变性(通常应该最小化)。但也因为人们倾向于使用 getters+setters 公开 class 的属性,而没有考虑适当的抽象。

具体来说:如果你有两个相互依赖的变量 xy,那么 class 应该只是 而不是 有方法

public void setX(int x) { ... }
public void setY(int y) { ... }

相反,应该(充其量,粗略地)有一种方法,如

public void setState(int x, int y) { 
    if (inconsistent(x,y)) throw new IllegalArgumentException("...");
    ...
}

确保状态始终一致。


我认为没有一种方法可以完全模拟 C++ friend 函数。您建议的 Consumer/Supplier 方法 可能 是一种合理的解决方法。它可能导致的一些(不是全部)问题可以通过稍微不同的方法来避免:

org.example 包含您的主要 class

class A {
    private int v;
    private int w;

    public void accept(SomeVisitor visitor) {
        // See below...
    }        
}

并且包org.example也包含一个接口。此接口使用 getter+setter 方法公开 A 的内部状态:

public interface InnerA {
    void setV(int v);
    int getV();
    void setW(int w);
    int getW();
}

但请注意,主要 class 不会 实现此接口!

现在,访客可以居住在不同的包裹中,例如 org.example.visitors。访问者可以有一个专门的方法来访问 InnerA 对象:

public class SomeVisitor extends ParentVisitor {

    @Override
    public void visit(A a) {
        ...
    }

    @Override
    public void visit(InnerA a) {
        // Code that will, possibly, read and modify A.x
        ...
    }

Aaccept 方法的实现可以执行以下操作:

    public void accept(SomeVisitor visitor) {

        visitor.accept(this);

        visitor.accept(new InnerA() {
            @Override 
            public void setX(int theX) {
                x = theX;
            }
            @Override 
            public int getX() {
                return x;
            }
            // Same for y....
        });
    }        

所以 class 会专门将新创建的 InnerA 实例传递给访问者。这个InnerA只会在访问时存在,并且只会用于修改创建它的特定实例。

一个中间解决方案可能是不定义这个接口,而是引入像

这样的方法
@Override
public void visit(Consumer<Integer> setter, Supplier<Integer> getter) {
    ...
}

@Override
public void visit(A a, Consumer<Integer> setter, Supplier<Integer> getter) {
    ...
}

需要根据真实应用案例进一步分析。

但同样:None 这些方法将规避一般问题,即当您提供对包外的 某人 的访问权限时,您将提供对 包裹外的每个人....


附录 1:class 是 A,但具有 public getter/setter 方法。再见,封装:

class AccessibleA extends A {
    private Consumer<Integer> setter;
    ...
    AccessibleA() {
        EvilVisitor e = new EvilVisitor();
        e.accept(this);
    } 
    void setSetter(Consumer<Integer> setter) { this.setter = setter; }
    ...
    // Here's our public setter now:
    void setValue(int i) { setter.accept(i); }
}

class EvilVisitor {
    private AccessibleA accessibleA;
    ...
    public void setSetter(Consumer<Integer> setter) {
        accessibleA.setSetter(setter);
    }
    ...
}

附录 2:

想象一下你有一个像这样的class

class Manipulator {
    private A a;
    Manipulator(A a) {
        this.a = a;
    }
    void manipulate() {
        int value = a.getValue();
        a.setValue(value + 42);
    }
}

现在假设您想删除此 class 对 class A 的编译时依赖性。然后您可以将其更改为在构造函数中不接受 A 的实例,而是接受 Supplier/Consumer 对。但是对于访客来说,这没有意义。