在 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 的属性,而没有考虑适当的抽象。
具体来说:如果你有两个相互依赖的变量 x
和 y
,那么 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
...
}
A
中 accept
方法的实现可以执行以下操作:
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
对。但是对于访客来说,这没有意义。
我有一个 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 的属性,而没有考虑适当的抽象。
具体来说:如果你有两个相互依赖的变量 x
和 y
,那么 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
...
}
A
中 accept
方法的实现可以执行以下操作:
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
对。但是对于访客来说,这没有意义。