class 中的不变性与状态变化
Immutability vs state change in a class
我对不可变 classes 的概念还很陌生。
考虑这个 class:
public class ConnectionMonitor implements MessageConsumer {
private final MonitorObject monitorObject;
private boolean isConnected = true;
private final static Logger logger = LogManager.getLogger(ConnectionMonitor.class);
public ConnectionMonitor(final MonitorObject monitorObject) {
this.monitorObject = monitorObject;
}
public boolean isConnected() {
return isConnected;
}
public void waitForReconnect() {
logger.info("Waiting for connection to be reestablished...");
synchronized (monitorObject) {
enterWaitLoop();
}
}
private void enterWaitLoop() {
while (!isConnected()) {
try {
monitorObject.wait();
} catch (final InterruptedException e) {
logger.error("Exception occured while waiting for reconnect! Message: " + e.getMessage());
}
}
}
private void notifyOnConnect() {
synchronized (monitorObject) {
monitorObject.notifyAll();
}
}
@Override
public void onMessage(final IMessage message) {
if (message.getType() == IMessage.Type.CONNECTION_STATUS) {
final String content = message.getContent();
logger.info("CONNECTION_STATUS message received. Content: " + content);
processConnectionMessageContent(content);
}
}
private void processConnectionMessageContent(final String messageContent) {
if (messageContent.contains("Disconnected")) {
logger.warn("Disconnected message received!");
isConnected = false;
} else if (messageContent.contains("Connected")) {
logger.info("Connected message received.");
isConnected = true;
notifyOnConnect();
}
}
}
我正在尝试了解如何将此 class 更改为不可变的。
特别是,我看不出如何将布尔字段 isConnected
设为最终值,因为它代表连接状态。
ConnectionMonitor
的所有客户应该只查询
isConnected()
获取连接状态。
我知道可以锁定对 isConnected
的更改或使用原子布尔值。
但我不知道如何将其重写为不可变的 class。
在教科书或范式狂热地窖之外,没有任何现实生活中的对象(不考虑单一用途时 classes)是 100% 不可变或无状态的。那些一有机会就在博览会上宣扬不变性的人通常非常喜欢他们的声音。总会有某种程度的可变性,但这里的关键是以安全和非暴力的方式进行,而不是仅仅因为改变对象。如果你能推断出为什么对象的某些部分应该是可变的,并且你事先知道所有可能的副作用,那么它是可变的就完全没问题了。正如您所说,布尔值 isConnected
需要具有 setter 和 getter 或其他一些机制来反映当前的连接状态。因为它基本上是一个只有两个状态的二进制开关,所以它可变的影响很小。您可以通过其他方式获得相同的结果,但您必须通过不同 class.
的局外人方法来实现它。
TL;DR:可变状态本身并不是邪恶的,但它可能被滥用来造成极大的邪恶。
理想的是最小化可变性——而不是消除它。
不可变对象有很多优点。它们简单、线程安全并且可以自由共享。
然而,有时我们需要可变性。
在 "Effective Java," 中,Joshua Bloch 建议这些准则:
- Classes should be immutable unless there's a very good reason to make them mutable.
- If a class cannot be made immutable, limit its mutability as much as possible.
在您的示例中,class 的实例可变是有充分理由的。但您也可以看到第二条准则在起作用:字段 monitorObject
被标记为最终字段。
只需将该状态放在其他地方即可。但是对于您的情况,这样做合乎逻辑吗?你应该问问自己。
也许最好让 ConnectionMonitor
可变。它负责 "monitoring" 一个连接,因此它必须跟踪可能更改的值。 否则,您将需要另一个 是 可变的对象跟踪该状态。
如果这还不够令人信服,那么这里有一些方法:
您可以为您的监视器创建一个容器 class,它将 ConnectionMonitors 映射到它的 ConnectionState:
class MonitorManager {
Map<ConnectionMonitor, Boolean> connectionStatuses = ...;
}
为简单起见,您可以将此管理器传递给每个监视器,允许侦听器访问地图并更改该连接的布尔值:
class ConnectionMonitor {
private MonitorManager manager;
//....
private void processConnectionMessageContent(final String messageContent) {
if (messageContent.contains("Disconnected")) {
logger.warn("Disconnected message received!");
manager.connectionStatuses.put(this, false);
} else if (messageContent.contains("Connected")) {
logger.info("Connected message received.");
manager.connectionStatuses.put(this, true);
notifyOnConnect();
}
}
}
但是有些开发人员会排队告诉您子对象不应该知道它们的容器。
因此创建一个新对象,负责在监视连接时收集的数据:
class MonitorManager {
private Map<ConnectionMonitor, MonitoredData> data = ...;
public void createMonitor() {
MonitoredData data = new MonitoredData();
this.data.put(new ConnectionMonitor(data), data);
}
}
class ConnectionMonitor inplements MessageConsumer {
private MonitoredData data;
public ConnectionMonitor(MonitoredData data) {
this.data = data;
}
//...
private void processConnectionMessageContent(final String messageContent) {
if (messageContent.contains("Disconnected")) {
logger.warn("Disconnected message received!");
data.setConnected(false);
} else if (messageContent.contains("Connected")) {
logger.info("Connected message received.");
data.setConnected(true);
notifyOnConnect();
}
}
}
class MonitoredData {
private boolean connected;
public void setConnected(boolean connected) {
this.connected = connected;
}
public boolean isConnected() {
return connected;
}
}
也许 MonitoredData
中的详细信息更适合正在监视的对象。如果您提供更多背景信息,会更容易提供帮助。
我对不可变 classes 的概念还很陌生。 考虑这个 class:
public class ConnectionMonitor implements MessageConsumer {
private final MonitorObject monitorObject;
private boolean isConnected = true;
private final static Logger logger = LogManager.getLogger(ConnectionMonitor.class);
public ConnectionMonitor(final MonitorObject monitorObject) {
this.monitorObject = monitorObject;
}
public boolean isConnected() {
return isConnected;
}
public void waitForReconnect() {
logger.info("Waiting for connection to be reestablished...");
synchronized (monitorObject) {
enterWaitLoop();
}
}
private void enterWaitLoop() {
while (!isConnected()) {
try {
monitorObject.wait();
} catch (final InterruptedException e) {
logger.error("Exception occured while waiting for reconnect! Message: " + e.getMessage());
}
}
}
private void notifyOnConnect() {
synchronized (monitorObject) {
monitorObject.notifyAll();
}
}
@Override
public void onMessage(final IMessage message) {
if (message.getType() == IMessage.Type.CONNECTION_STATUS) {
final String content = message.getContent();
logger.info("CONNECTION_STATUS message received. Content: " + content);
processConnectionMessageContent(content);
}
}
private void processConnectionMessageContent(final String messageContent) {
if (messageContent.contains("Disconnected")) {
logger.warn("Disconnected message received!");
isConnected = false;
} else if (messageContent.contains("Connected")) {
logger.info("Connected message received.");
isConnected = true;
notifyOnConnect();
}
}
}
我正在尝试了解如何将此 class 更改为不可变的。
特别是,我看不出如何将布尔字段 isConnected
设为最终值,因为它代表连接状态。
ConnectionMonitor
的所有客户应该只查询
isConnected()
获取连接状态。
我知道可以锁定对 isConnected
的更改或使用原子布尔值。
但我不知道如何将其重写为不可变的 class。
在教科书或范式狂热地窖之外,没有任何现实生活中的对象(不考虑单一用途时 classes)是 100% 不可变或无状态的。那些一有机会就在博览会上宣扬不变性的人通常非常喜欢他们的声音。总会有某种程度的可变性,但这里的关键是以安全和非暴力的方式进行,而不是仅仅因为改变对象。如果你能推断出为什么对象的某些部分应该是可变的,并且你事先知道所有可能的副作用,那么它是可变的就完全没问题了。正如您所说,布尔值 isConnected
需要具有 setter 和 getter 或其他一些机制来反映当前的连接状态。因为它基本上是一个只有两个状态的二进制开关,所以它可变的影响很小。您可以通过其他方式获得相同的结果,但您必须通过不同 class.
TL;DR:可变状态本身并不是邪恶的,但它可能被滥用来造成极大的邪恶。
理想的是最小化可变性——而不是消除它。
不可变对象有很多优点。它们简单、线程安全并且可以自由共享。
然而,有时我们需要可变性。
在 "Effective Java," 中,Joshua Bloch 建议这些准则:
- Classes should be immutable unless there's a very good reason to make them mutable.
- If a class cannot be made immutable, limit its mutability as much as possible.
在您的示例中,class 的实例可变是有充分理由的。但您也可以看到第二条准则在起作用:字段 monitorObject
被标记为最终字段。
只需将该状态放在其他地方即可。但是对于您的情况,这样做合乎逻辑吗?你应该问问自己。
也许最好让 ConnectionMonitor
可变。它负责 "monitoring" 一个连接,因此它必须跟踪可能更改的值。 否则,您将需要另一个 是 可变的对象跟踪该状态。
如果这还不够令人信服,那么这里有一些方法:
您可以为您的监视器创建一个容器 class,它将 ConnectionMonitors 映射到它的 ConnectionState:
class MonitorManager {
Map<ConnectionMonitor, Boolean> connectionStatuses = ...;
}
为简单起见,您可以将此管理器传递给每个监视器,允许侦听器访问地图并更改该连接的布尔值:
class ConnectionMonitor {
private MonitorManager manager;
//....
private void processConnectionMessageContent(final String messageContent) {
if (messageContent.contains("Disconnected")) {
logger.warn("Disconnected message received!");
manager.connectionStatuses.put(this, false);
} else if (messageContent.contains("Connected")) {
logger.info("Connected message received.");
manager.connectionStatuses.put(this, true);
notifyOnConnect();
}
}
}
但是有些开发人员会排队告诉您子对象不应该知道它们的容器。
因此创建一个新对象,负责在监视连接时收集的数据:
class MonitorManager {
private Map<ConnectionMonitor, MonitoredData> data = ...;
public void createMonitor() {
MonitoredData data = new MonitoredData();
this.data.put(new ConnectionMonitor(data), data);
}
}
class ConnectionMonitor inplements MessageConsumer {
private MonitoredData data;
public ConnectionMonitor(MonitoredData data) {
this.data = data;
}
//...
private void processConnectionMessageContent(final String messageContent) {
if (messageContent.contains("Disconnected")) {
logger.warn("Disconnected message received!");
data.setConnected(false);
} else if (messageContent.contains("Connected")) {
logger.info("Connected message received.");
data.setConnected(true);
notifyOnConnect();
}
}
}
class MonitoredData {
private boolean connected;
public void setConnected(boolean connected) {
this.connected = connected;
}
public boolean isConnected() {
return connected;
}
}
也许 MonitoredData
中的详细信息更适合正在监视的对象。如果您提供更多背景信息,会更容易提供帮助。