java - 如何防止修改可变对象
问题描述
假设我有一个简单的类:
class Some {
private Something something;
public Some() {}
public Something getSomething() {
return this.something;
}
public void setSomething(Something something) {
this.something = something;
}
}
我有一个方法:
void change(Some some) {
//change state of object via
some.setSomething(new Something());
}
如何防止 Some 对象的状态在change()
方法内部发生变化?
条件:不能修改Some
类(即删除setSomething()
方法,将something
字段设为final
,将类声明为final
)
我找到的解决方案:
interface ISome {
void setSomething(Something something);
Something getSomething();
}
class Some implements ISome {
private Something something;
public Some() {}
public Something getSomething() {
return this.something;
}
public void setSomething(Something something) {
this.something = something;
}
}
class SomeProxy implements ISome {
private ISome some;
public SomeProxy(ISome some) {
this.some = some;
}
public Something getSomething() {
return some.getSomething();
}
public void setSomething(Something something) {
throw new IllegalOperationException("You cant modify an object!");
}
}
方法:
void change(ISome some) {
some.setSomething(new Something());
}
然后如果我们这样调用change()
方法:
change(new SomeProxy());
我们会得到一个例外。
java机制中的什么地方可以帮助解决这个问题并防止对象在change()
方法内部被修改而不创建代理?
解决方案
没有设置器的接口
我同意@kaya3 的评论,只需定义一个没有设置器的接口并将其提供给您的函数:
interface ISome {
Something getSomething();
}
class Some implements ISome {
private Something something;
public Some() {}
public Something getSomething() {
return this.something;
}
public void setSomething(Something something) {
this.something = something;
}
}
void change(ISome some) {
//Doesn't compile there no method setSomething in the interface
some.setSomething(new Something());
}
请注意,您仍然修改了 Some 类,您使其实现了一个接口,如果该类来自一个库并且您真的无法触摸它,它就不起作用。我使用它是因为你自己做了,但如果你真的不能修改类,这仍然不是一个很好的解决方案。
这仍然是隔离接口和实现的绝佳解决方案。该接口是您的公共 API 的一部分,没有人可以使用公共接口修改对象。所以 API 总是返回接口的实例,但使用内部的具体实现。
一个真正不触及原始类的代理
如果类来自外部 API,您可以通过不让 Some 实现接口并引入代理来更进一步:
class Some {
private Something something;
public Some() {}
public Something getSomething() {
return this.something;
}
public void setSomething(Something something) {
this.something = something;
}
}
class SomeProxy implements ISome {
private Some some;
public SomeProxy(Some some) {
this.some = some;
}
public Something getSomething() {
return some.getSomething();
}
}
void change(ISome some) {
//Doesn't compile there no method setSomething in the interface
some.setSomething(new Something());
}
这样,您永远不会更改 Some 类,但要正常工作,您必须确保 Some 类在客户端代码中不可见。您可以通过 jave 9 模块、OSGI 或 maven 中的运行时依赖性来实现这一点。
代理只是更通用和常见的一种特定情况
如果您考虑一下,这是一个特定情况,即只有一个具有私有字段的类供自己使用,并发布该对象行为的一部分但不是全部。这个类不仅需要这样做。她可能有一个不是代理的目的,而只是碰巧有一个 Some 实例来完成它的工作。她控制 Some 实例,可以调用 setter 因为她处于控制之中,但对于外部世界,她只授予对 setter 的访问权限。
这是一般情况。该类确实实现了一个概念/功能并正确封装了它的内部状态和实现。如果你将该类传递给任何函数,没有人可以以不需要的方式修改内部状态:
class OtherBehavior {
private Some some;
private OtherThing thing;
[...]
public Something getSomething() {
return some.getSomething();
}
public void doStuff() {
[...]
}
[...]
}
void change(OtherBehavior other) {
// Doesn't compile:
other.setSomething(new Something());
// Doesn't compile:
other.some.setSomething(new Something());
// Ok:
other.getSomething();
}
在一般情况下,请首选不可变设计
通常认为,在许多情况下,不可变设计总体上更好。因此允许构造函数获取 Something() 实例,然后将字段设为 final 并且不提供 setter。您会发现,在很多情况下,您根本不需要 setter,只需在 Something 实例可用时创建不可变对象即可。这是更简单和健壮的设计。如果您的 API 需要“更改”为新的 Something,您也可以随时创建一个新的 Some 实例:
class Some {
private final Something something;
public Some(Something something) {
this.something = something;
}
public Something getSomething() {
return this.something;
}
}
这有以下好处:
- 您可以添加 equals/hashCode 并使其安全地参与映射/集合集合,您甚至可以在构建时计算哈希并将其缓存以提高性能(在性能敏感代码方面帮助了我们很多)
- 您可以安全地围绕线程共享对象
- 您可以防止客户端调用该 setter 发生错误,因此您可以将它传递到任何地方而没有任何错误风险。
- 您根本不需要复杂的代理,复制或处理异常。
但这当然意味着您可以控制 Some 类。
推荐阅读
- sql - 检查列是否有空值
- sql - <-> 运算符的文档/定义?
- java - 无法解析方法“putExtra(java.lang.String,com.example.talkmore.models.User)”
- git - 如何使用正确的 Git Diff 创建三级功能分支
- stylelint - Stylelint - 使 CSS 在子类定义之前出现
- amazon-s3 - 增量表中的查询
- snowflake-cloud-data-platform - 雪花错误:SQL 访问控制错误:权限不足,无法操作帐户'
' - scala - 如何在Scala的for循环中跳过迭代?
- python - 如果使用 JSON 字段,则选择太长
- powershell - 如果条件不匹配,则使用 powershell 重试命令