在Java中使用消费者作为setter和供应商作为getter是不好的做法吗?< / h1 >



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

的例子:

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。


更多细节:

我有一些课程将利用访客。这些类具有相互依赖的私有变量。如果这些变量有设置,当用户改变这些变量时,不一致可能会出现,这些变量应该是相互依赖的,而不尊重这些依赖关系。

这些变量中的一些有getter,其他的没有,因为它们只会在内部使用,不应该在其他地方访问。访问者是一个例外,应该对这些变量具有读/写访问权限,原因是访问者打算实现的功能是打算在这些类的方法中实现的。但我想如果我用访客会更干净。这些功能确实需要对这些变量进行读/写访问。

这种方法背后的意图是模仿c++中的友元特性。我可以将访问者放在与这些类相同的包中(如果我没有找到这个问题的整洁解决方案,我会这样做);但是我认为如果有游客的话,这个包装看起来会很乱(当然会有很多游客)。


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

我试着把它塞进评论里,因为从技术上讲,它并没有回答关于这是否是"不良实践™"的问题,但是这个术语很难定义,因此,无论如何都几乎不可能给出答案…

这最终似乎归结为如何使java方法仅对特定类可见的问题(还有类似的问题)。getter/setter应该只对一个特定的类可用——也就是对访问者可用。

你在问题中使用了非常通用的名称和描述,很难说这是否有意义。

但有几点需要考虑:

  • 有人可能会认为这通常会破坏封装。每个人都可以编写这样的访问器并访问get/set方法。尽管这可能是一个荒谬的hack:如果人们想要实现一个目标,他们就会这样做!(草图见下文附录1)

  • 一般来说,有人会说:为什么是否只有访问者可以访问setter/getter,而其他类则不能?

  • 将getter/setter方法隐藏在Supplier/Consumer实例后面的一个令人信服的原因可以与类的可见性和特殊性相关(在附录2中详细阐述)。但是由于访问者总是依赖于被访问的类,所以这在这里不直接适用。

  • 有人可能会说这种方法更容易出错。假设setter和getter都是null,或者它们属于不同的实例。调试这个可能非常困难。

  • 正如在评论和其他回答中看到的那样:有人可能会争辩说,提议的方法只会使事情复杂化,并且"隐藏"了这些实际上是setter/getter方法的事实。我不会说setter/getter方法通常已经是个问题了。但你现在的方法是在访问者中使用setter-setter和getter-setter。这扩展了状态空间以一种难以理解的方式对待来访者。

总结:

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


更新对于添加的细节:You said that

当用户更改这些变量时可能会出现不一致

一个类通常有责任管理它自己的状态空间,以确保它总是"一致的"。在某种意义上,这就是的主要目的类和封装的重要性。getter +setter有时被认为是"邪恶"的原因之一不仅仅是可变性(这通常应该最小化)。而且还因为人们倾向于使用getter +setter来公开类的属性,而没有考虑适当的抽象。

所以具体来说:如果你有两个变量xy相互依赖,那么类应该简单地而不是有方法

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

但是请注意,主类执行而不是实现这个接口!

现在,访客可以驻留在不同的包中,如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....
});
}        

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

一个介于两者之间的解决方案可能是不定义这个接口,但引入像 这样的方法
@Override
public void visit(Consumer<Integer> setter, Supplier<Integer> getter) {
...
}

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

必须根据实际应用案例进一步分析。

但是,同样,这些方法都不能避免一个普遍的问题,即当您在包外提供对某人的访问权限时,那么您将在包外提供对所有的访问权限....


附录1:一个A类,但具有公共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 Manipulator {
private A a;
Manipulator(A a) {
this.a = a;
}
void manipulate() {
int value = a.getValue();
a.setValue(value + 42);
}
}

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

反正getter和setter都是邪恶的,所以最好不要让事情比普通的getter和setter更复杂。

最新更新