跳到主要内容

属性变更监听

介绍

属性可添加监听器,当某些属性值的发生变化时,触发监听器。


属性监听特点

  • 可为属性添加监听器,用于观察属性值的变化。
  • 属性可以添加多个监听器。
  • 属性的监听器可以移除。

框架已经内置了几个属性实现类,分别是

  • IntegerProperty
  • LongProperty
  • StringProperty
  • BooleanProperty
  • ObjectProperty

除了已有的内置实现类,开发者也可以自己扩展更多的类型。

使用场景

使用场景 1

比如玩家的血量低于一定值时,需要触发无敌状态。 此时,我们就可以监听玩家的血量,并在该属性上添加一个对应的监听器来观察血量的变化,当达到预期值时就触发对应的业务。


使用场景 2

我还可以通过该特性来管理房间状态,当房间状态变更时,监听器可以执行相应的业务。


类似的使用场景还有很多,这里就不过多的举例了,属性监听的特点在于属性变化后会触发监听器。

如何使用

BooleanProperty

当 BooleanProperty 对象的值发生改变时,触发监听器。

  • code 2,添加一个监听器。
  • code 7,值变更时,将会触发监听器
var property = new BooleanProperty();
property.addListener((observable, oldValue, newValue) -> {
log.info("oldValue:{}, newValue:{}", oldValue, newValue);
});

property.get(); // value is false
property.set(true);
property.get(); // value is true

IntegerProperty

当 IntegerProperty 对象的值发生改变时,触发监听器。

  • code 2,添加一个监听器。
  • code 7,值变更时,将会触发监听器。
  • code 10,IntegerProperty 增加值的便捷方法。
var property = new IntegerProperty();
property.addListener((observable, oldValue, newValue) -> {
log.info("oldValue:{}, newValue:{}", oldValue, newValue);
});

property.get(); // value is 0
property.set(22);
property.get(); // value is 22

property.increment(); // value is 23. will trigger listeners

ObjectProperty

当 ObjectProperty 对象内值的引用发生改变时,触发监听器。

  • code 4,添加一个监听器。
  • code 8,不会触发监听器,因为引用没有发生变更。
  • code 11,引用变更,触发监听器。
YourUser user = new YourUser();

var property = new ObjectProperty(user);
property.addListener((observable, oldValue, newValue) -> {
log.info("oldValue:{}, newValue:{}", oldValue, newValue);
});

property.set(user); // does not trigger listeners

YourUser user2 = new YourUser();
property.set(user2);
property.get(); // value is user2

class YourUser {}

移除监听器

下面这个示例,我们将 property 初始值设置为 10,随后添加了一个监听器;当监听器观察到新值为 9 时,就从 observable 中移除自己(这个自己指的是监听器本身),而 observable 则是 IntegerProperty。

  • code 4,添加一个监听器。
  • code 10,移除当前监听器。
  • code 15,值减 1,当前值为 9,将会触发监听器。
  • code 16,值减 1,当前值为 8,但不会触发监听器,因为监听器已经被移除了。
public void remove1() {
IntegerProperty property = new IntegerProperty(10);

property.addListener(new PropertyChangeListener<>() {
@Override
public void changed(PropertyValueObservable<? extends Number> observable, Number oldValue, Number newValue) {
log.info("1 - newValue : {}", newValue);

if (newValue.intValue() == 9) {
observable.removeListener(this);
}
}
});

property.decrement(); // the value is 9
property.decrement(); // the value is 8,由于监听器已经移除,所以不会触发任何事件。
}

下面的示例中,我们定义了一个监听器类 OnePropertyChangeListener 并实现了 PropertyChangeListener 监听器接口。示例中,我们通过 OnePropertyChangeListener 对象的引用来移除监听器。

  • code 2,创建自定义的监听器。
  • code 5,添加监听器。
  • code 7,值加 1,当前值为 1,并触发监听器。
  • code 8,移除监听器
  • code 9,值加 1,当前值为 2,但不会触发监听器,因为监听器已经被移除了。
  • code 12,自定义的监听器。
public void remove2() {
OnePropertyChangeListener onePropertyChangeListener = new OnePropertyChangeListener();

IntegerProperty property = new IntegerProperty();
property.addListener(onePropertyChangeListener);

property.increment(); // the value is 1
property.removeListener(onePropertyChangeListener);
property.increment(); // the value is 2
}

class OnePropertyChangeListener implements PropertyChangeListener<Number> {
@Override
public void changed(PropertyValueObservable<? extends Number> observable, Number oldValue, Number newValue) {
log.info("oldValue:{}, newValue:{}, observable:{}", oldValue, newValue, observable);
}
}

小结

属性监听在使用上是简单的,如果你的业务中有关于属性变化后需要触发某些事件的, 可以考虑使用该特性。框架为 int、long、boolean、Object、String 等基础类型提供了对应的属性监听。

属性监听特性支持添加多个监听器,支持移除监听器。

扩展阅读

案例,监听房间的游戏状态

监听不同的房间游戏状态,来触发业务方法。

public class FairRoom extends SimpleRoom {
...
final ObjectProperty<GameStatus> gameStatus = new ObjectProperty<>(GameStatus.NONE);

private void extractedInitGameStatus() {
this.gameStatus.addListener((observable, oldValue, newValue) -> {
switch (newValue) {
case START -> nextRound();
case SETTLE -> settle();
}
});
}

public void changeGameStatus(GameStatus status) {
this.executeTask(() -> this.gameStatus.set(status));
}

public void changeGameStatus(GameStatus status, long millis) {
this.executeDelayTask(() -> this.gameStatus.set(status), millis);
}
}

public enum GameStatus {
NONE,
START,
SETTLE
}

案例,与动态属性结合

这是一个属性监听与动态属性结合的一个案例。


业务描述

see https://github.com/iohao/ioGame/issues/259

该 Issue 想在模拟客户端中通过记录心跳发送的次数,当心跳触发到一定次数后,触发一些自定义逻辑,如关闭模拟客户端。

该业务我们可以属性监听与动态属性结合,在模拟客户端中,我们为 clientUser 添加一个动态属性 idleCounter。 idleCounter 是 IntegerProperty 类型的,我们为该属性添加了一个监听事件,当 idleCounter 心跳值大于 10 时,就让模拟客户端与服务器断开。

可以看出,当属性监听与动态属性结合后,可以的轻松实现这一特殊需求。

由于模拟客户端本身没有提供心跳记录的属性,那么我们就通过动态属性为该 clientUser 提供一个属性,用于记录心跳值。 而业务中又想让心跳值达到一定次数后触发自定义的业务,那么我们就通过 xxxProperty(属性监听)来监听心跳值的变化。

public static void main(String[] args) {
int idlePeriod = 3;
// 模拟的玩家
var clientUser = getDefaultClientUser(idlePeriod);

new ClientRunOne()
.setClientUser(clientUser)
// 开启心跳,每 N 秒向服务器发送一次心跳消息
.idle(idlePeriod)
.startup();
}

private static DefaultClientUser getDefaultClientUser(int idlePeriod) {
// 动态属性 key
AttrOption<IntegerProperty> idleOption = AttrOption.valueOf("idleOption");
// 模拟的心跳计数器
IntegerProperty idleCounter = new IntegerProperty();
// 添加一个监听器,当值发生变化时会触发监听器
idleCounter.addListener((observable, oldValue, newValue) -> {
if (newValue.intValue() > 10) {
clientUser.getClientUserChannel().closeChannel();
}
});

// 给 clientUser 添加一个动态属性
var clientUser = new DefaultClientUser();
clientUser.option(idleOption, idleCounter);

// 每 N 秒执行一次
TaskKit.runInterval(() -> {
// 心跳计数器 + 1,每次值变更,并触发监听
clientUser.ifPresent(idleOption, IntegerProperty::increment);
}, idlePeriod, TimeUnit.SECONDS);

return clientUser;
}