Akka. 如何将业务逻辑从Actor中分离?

4

我是一个Akka的新手,对这项技术感到兴奋,但是我不明白如何在没有业务逻辑的情况下编写Actor。假设我需要制作一款游戏。游戏有玩家数量限制,并且在选择赢家后不能再次选择。

public class Game {

    private List<String> participants;
    private int maxParticipants;
    private String winner;
    private boolean ended;

    public int getMaxParticipants() {
        return maxParticipants;
    }

    public int getParticipantsSize() {
        return participants.size();
    }

    public void addParticipant(String participant) {

        participants.add(participant);
    }

    public boolean isEnded() {
        return ended;
    }

    public void chooseWinner() {
        if (ended) {
            // do some stuff
        }
        winner = participants.get(randomNumber);
        ended = true;
    }

}

并且演员类:

public class GameActor extends UntypedActor {

    private Game game = new Game();

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof AddParticipant) {
            // For example there can't be more then two participants.
            // I want to have this "if" inside my Game class, not here
            if (game.getParticipantsSize() >= game.getMaxParticipants()) {
                // do something about it
            }
            game.addParticipant(message.getParticipant());
        }else if (message instanceof ChooseWinner){
            // We can't choose winner after game ended. I don't want to have this logic here
            game.chooseWinner();
        }
    }
}

目前我看到了几种方法。在简单的情况下,它们都可以工作,但它们非常有限:

  1. 触发异常。只在负面情况下有效。如果一切正常,我不知道下一步该做什么。同时,catch块很丑陋,我不想维护诸如GameFullException、GameEndedException等。

  2. 返回某个值。例如,在addParticipant中,如果成功,则返回布尔值。要么使用有限,要么为返回值添加另一层ifelse。

  3. Game类可以触发事件,我的actor可以订阅它。

就像这样:

public class Game {

    private List<Listener> listeners = new ArrayList<>();

    public void addListener(Listener listener) {
        listeners.add(listener);
    }

    public void riseEvent(Event event) {
        listeners.forEach(l->l.handle(event));
    }

}

单个监听器是执行者:

public class GameActor extends UntypedActor {

    private Game game = new Game();

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof AddParticipant) {
            game.addParticipant(message.getParticipant());
        }else if (message instanceof ChooseWinner){
            game.chooseWinner();
        }else if( message instanceof Event){
            // do something with actor state if needed
        }
    }

    public void handle(Event event) {
        self().tell(event, self());
    }
}

第三个方案对我来说似乎最有趣和最强大,但是我的模型只有一个订阅者,而且它是一个演员,反过来又向自己发送由模型引发的事件,这似乎不太对。 当然,这个游戏类只是一个示例(我不确定它是否是我问题的好示例),这样简单的逻辑可以在演员中完成,这是可以的,但是我对如何将业务逻辑与演员解耦的原则感兴趣,因为我认为演员并不是处理业务逻辑的好地方,原因有很多。 我对Scala和Java都很感兴趣。如果能提供一些示例就更好了。
2个回答

3

为什么你应该让你的角色演员没有业务逻辑?封装状态正是演员擅长的。在你的例子中,我会抛弃Game类,并将其字段放置在GameActor中,像这样:

public class GameActor extends UntypedActor {

private List<String> participants;
private int maxParticipants;
private String winner;
private boolean ended;

@Override
public void onReceive(Object message) throws Exception {
    if (message instanceof AddParticipant) {
        //In my opinion, its perfectly valid to have this here
        if (this.participants.size() >= this.maxParticipants) {
            // do something about it
        }
        this.participant.add(message.getParticipant());
    }else if (message instanceof ChooseWinner){
        //again I why not put the logix here?
        if (this.ended)
            // Handle this error somehow.
        else
           this.winner = messag.getWinner();
           this.ended = true;
    }
}

但是,如果你想将其分成两个类,也可以,但它们都应该是actors。例如,您可以将其分为一个actor来保存游戏状态(有点像您的游戏类,但这也应该是一个actor),以及执行逻辑的actor在状态更改时应该发生什么副作用。

保存状态的actor可以通过不同的方式通知逻辑actor。最简单的方法是只回复发送者当状态已更改时。如果总是actor发送状态更改请求,那么应该在状态更改时做一些事情,这将起作用。但是,如果有更多的actors应该对游戏状态的变化作出反应,则可以发送消息到akkas eventstream,并且所有已订阅该消息的actors都将接收事件。


你的回答也非常有用,但我只能接受一个。无论如何,非常感谢你。 - Artem Malinko

1
但是演员无疑是放置业务逻辑的地方。演员是并发系统的主要业务处理实体。在Erlang中,演员只是在接收到消息时运行的程序。如果您想要一个中央实体来处理消息的内容,那么您必须将消息从演员中委托出去。
我想提出的另一个评论是关于您的规则3的想法。我强烈反对这样做,因为这正是Akka所应该做的相反的想法。如果您的游戏需要向GameActor发送消息,则应使您的Game类本身扩展Actor,然后通过Akka逻辑发送消息。
换句话说,Akka的主要思想就是使业务演员之间的并发通信变得容易。将业务逻辑与演员分离,就是将Akka解构为不太有用的东西的过程。

也许你是对的,我只需要进行一点思维转变。起初,我认为像演员这样的东西只是另一种同步技术。主要原因是我不想在演员中放置业务逻辑,因为演员比简单的函数和类更难测试。但也许这是我为了简单的并发性所需要付出的代价。 - Artem Malinko
1
我能理解你为什么这么说,但我认为你是不正确的。演员非常容易测试。向他们发送消息,然后测试他们的反应!并发问题是无法避免的——如果您想让一个类同时接收多个消息,则扩展Actor类型可能是允许它们参与的最简单机制。 - Richard Løvehjerte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接