孩子对象到父母的通信模式 [英] Communication pattern for child objects to parents

查看:136
本文介绍了孩子对象到父母的通信模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我实际上是在C#中开发一个多线程游戏服务器。



到目前为止,我开始使用的模式类似于这样:



服务器类是现实的:




  • 用于启动和关闭服务器



OnStart服务器实例 ConnnectionManager




  • 包含 ConnectionPool 类的实例

  • 用于异步连接(通过TCPListener.BeginAcceptSocket)

  • 接受套接字时,会创建一个 ClientConnection 的实例,并将其添加到 ConnectionsPool



ConnectionsPool 类:




  • 负责添加和删除活动:





    b

    • 负责通过Socket.BeginReceive接收数据

    • 当接收到所有数据时, SocketHandler


    架构应如下所示:

    服务器 - > ConnectionManager - > ConnectionsPool - > ClientConnection - > SocketHandler



    问题

    SocketHandler,我怎么能影响另一个球员? (例如,玩家A命中玩家B:我需要在ConnectionsPool中获取玩家B的实例并更新他的HP属性),甚至更新服务器本身(比如调用Server类的关闭方法)



    我假设有3个选择:




    • 将所有重要的类(Server + ConnectionsPool) b $ b static classes>缺点:不能进行单元测试

    • 将所有重要的类转换为Singleton类>缺点:$ b​​ $ b难以测试

    • 在子构造函数中插入重要类的实例>
      缺点:传递大量信息后,可读代码较少





    我收到了一个建议,为孩子注入一个委托,这类似于第三种方法,但是我不确定单元测试这个和真正的效果,因为一切都是多线程当然。



    这里最好的架构选择是什么?

    解决方案

    我发现的网络代码不是真正的单元测试,应该与您希望测试的代码隔离Player对象,例如)。对我来说,似乎你已经将一个Player绑定到一个ClientConnection,这可能会使它不太可测试。我也认为将玩家与他的连接耦合可能违反了SRP,因为他们有非常独特的责任。



    我写了一个系统,看起来类似于你希望实现,我这样做是通过连接一个播放器到一个连接只通过代理和接口。我有一个单独的世界和服务器类库,第三个项目,应用程序,引用这两个,并创建一个服务器和世界的实例。我的播放器对象在世界和服务器中的连接 - 这两个项目不引用彼此,所以你可以考虑Player没有任何网络条款。



    我使用的方法是使SocketHandler抽象,并在Application项目中实现一个具体的处理程序。处理程序有一个对世界对象的引用,它可以在创建时传递给它。 (我通过工厂类 - 例如ClientConnectionFactory(在应用程序中),其中服务器只知道抽象连接和工厂)。

      public class ClientConnectionFactory:IConnectionFactory 
    {
    private readonly世界世界;

    public ClientConnectionFactory(World world){
    this.world = world;
    }

    连接IConnectionFactory.Create(套接字套接字){
    return new ClientConnection(socket,world);
    }
    }

    ClientConnection可以将World引用转发给它的处理程序,以及处理程序需要的有关Connection本身的任何信息,特别是发送方法。这里我只是将Connection对象本身传递给Handler。

      public ClientConnection(Socket socket,World world)
    : base(socket){
    this.handler = new ClientHandler(this,world);
    ...
    }

    游戏逻辑与然后将该网络包含在ClientHandler中,也可能包含在它引用的任何其他姐妹对象中。

      class ClientHandler:SocketHandler 
    {
    private readonly连接连接;
    private readonly世界世界;

    public ClientHandler(连接,世界){
    this.connection = connection;
    this.world = world;
    ...
    }

    //来自SocketHandler的重载方法
    public override void HandleMessage(Byte [] data){
    ...
    }
    }

    其余的包括ClientHandler创建它的Player对象,界面来更新动作,然后将玩家添加到世界上的玩家池。最初,我使用Player中的事件列表来做到这一点,我订阅了ClientHandler(知道Connection)中的方法,但事实证明,在Player对象中有几十个事件变成了一个糟糕的维护。



    我选择的选项是在播放器的World项目中使用抽象通知器。例如,对于移动,我将在Player中有一个IMovementNotifier对象。在应用程序中,我将创建一个ClientNotifier实现这个接口,并做相关的数据发送到客户端。 ClientHandler将创建ClientNotifier并将其传递给它的Player对象。

     类ClientNotifier:IMovementNotifier //,...,其他通知程序
    {
    private readonly连接连接;

    public ClientHandler(Connection connection){
    this.connection = connection;
    }

    void IMovementNotifier.Move(Player player,Location destination){
    ...
    connection.Send(new MoveMessage(...));
    }
    }

    ClientHandler ctor可以更改为实例化此通知程序。

      public ClientHandler(连接,世界){
    this.connection = connection;
    this.world = world;
    ...
    var notifier = new ClientNotifier(this);
    this.player = new Player(notifier);
    this.world.Players.Add(player);
    }

    因此,结果系统是ClientHandler负责所有传入消息和事件,ClientNotifier处理所有传出的内容。这两个类很难测试,因为它们包含很多其他的垃圾。网络不能真正被单元测试,但是这两个类中的Connection对象可以被嘲笑。世界图书馆是完全可单元测试的,没有任何考虑网络组件,这是我想要在我的设计中实现。



    这是一个大系统,不在这里覆盖了很多,但希望这可以给你一些提示。请问您是否需要任何更具体的信息。



    如果我可以给任何更多的建议,这将是避免任何静态或单身 - 他们会回来咬你。如果你需要作为一个替代,喜欢增加复杂性,但文档,你需要。我的系统可能看起来很复杂的维护者,但有很好的文件。对用户来说使用起来特别简单。主要本质上是

      var world = new World(); 
    var connectionFactory = new ClientConnectionFactory(world);
    var server = new Server(settings.LocalIP,settings.LocalPort,connectionFactory);


    I am actually developping a multithreaded game server in C#. I am trying to create it as unit-testable as possible.

    As of now, the pattern I started using resemble to something like this :

    A Server class is instanciated:

    • Serves to start and shutdown the server

    OnStart the Server instanciate the ConnnectionManager :

    • Contains an instance of the class ConnectionPool
    • Serves to accept asynchronously connections (via TCPListener.BeginAcceptSocket)
    • When a socket is accepted an instance of ClientConnection is created ans added to the list in ConnectionsPool

    The class ConnectionsPool :

    • Responsible to add and remove active connections to the pool, managed as a list

    The class ClientConnection :

    • Responsible to receive the data via Socket.BeginReceive
    • When all is received, an instance of SocketHandler is created (basically parse the socket content and run actions)

    The schema should look like this :
    Server -> ConnectionManager -> ConnectionsPool -> ClientConnection -> SocketHandler

    The problem :
    When I am inside the SocketHandler, how can I affect another player ? (for example, player A hits player B: I need to get the instance of the player B inside ConnectionsPool and update his HP property) or even update the server itself (say call the shutdown method on the Server class)

    I assume to have 3 choices :

    • Transform all important classes (Server + ConnectionsPool) into static classes > drawback: can't be unit tested
    • Transform all important classes into Singleton classes > drawback : difficult to test
    • Inject inside child constructor the instances of important classes > drawback : less readable code since a lot of information is passed

    The goal here is to make this all unit testable using best practices while keeping a simple approach.

    I received a suggestion to inject a delegate to childs which is similar to the 3rd method, but I am unsure about unit testing this and the real effect since everything is multithreaded of course.

    What would be the best architecture choice here ?

    解决方案

    The theme I find is that networking code is not really unit testable, and should be isolated from the code you do wish to test (the Player object, for example). To me it seems you have tightly coupled a Player to a ClientConnection, which might make it less testable. I also think that coupling a Player with his connection is probably violating the SRP, since they have very distinct responsibilities.

    I've written a system which seems similar to what you wish to acheive, and I done so by linking a player to a connection only via delegates and interfaces. I have a separate World and Server class library, and a third project, Application, which references the two, and creates an instance of Server and World. My Player object is in the World, and a Connection in the Server - these two projects do not reference one another, so you can consider Player without any terms of networking.

    The method I use is to make SocketHandler abstract, and implement a concrete handler in the Application project. The handler has a reference to the world object, which can be passed to it on creation. (I do so by means of factory classes - eg, ClientConnectionFactory (in Application), where the Server knows only of abstract Connections and Factories.)

    public class ClientConnectionFactory : IConnectionFactory
    {
        private readonly World world;
    
        public ClientConnectionFactory(World world) {
            this.world = world;
        }
    
        Connection IConnectionFactory.Create(Socket socket) {
            return new ClientConnection(socket, world);
        }
    }
    

    The ClientConnection can then forward the World reference to it's handler, along with any information the handler requires about the Connection itself, such as the Send methods in particular. Here I just pass the Connection object itself to the Handler.

    public ClientConnection(Socket socket, World world)
        : base(socket) {
        this.handler = new ClientHandler(this, world);
        ...
    }
    

    Most of the coordination between the game logic and the networking is then contained inside the ClientHandler, and perhaps any other sister objects it references.

    class ClientHandler : SocketHandler
    {
        private readonly Connection connection;
        private readonly World world;
    
        public ClientHandler(Connection connection, World world) {
            this.connection = connection;
            this.world = world;
            ...
        }
    
        //overriden method from SocketHandler
        public override void HandleMessage(Byte[] data) {
            ...
        }
    }
    

    The rest involves the ClientHandler creating it's Player object, assigning delegates or interfaces to update actions, and then adding the player to the pool of players in World. Initially I done this with a list of events in Player, which I subscribe to with methods in the ClientHandler (which knows of the Connection), but it turned out that there were dozens of events in the Player object which became a mess to maintain.

    The option I went for was to use abstract notifiers in the World project on the player. For example, for movement I would have an IMovementNotifier object in Player. In Application, I would create a ClientNotifier which implements this interface and does the relevant sending of data to the client. The ClientHandler would create the ClientNotifier and pass it to it's Player object.

    class ClientNotifier : IMovementNotifier //, ..., + bunch of other notifiers
    {
        private readonly Connection connection;
    
        public ClientHandler(Connection connection) {
            this.connection = connection;
        }
    
        void IMovementNotifier.Move(Player player, Location destination) {
            ...
            connection.Send(new MoveMessage(...));
        }
    }
    

    The ClientHandler ctor can be changed to instantiate this notifier.

    public ClientHandler(Connection connection, World world) {
        this.connection = connection;
        this.world = world;
        ...
        var notifier = new ClientNotifier(this);
        this.player = new Player(notifier);
        this.world.Players.Add(player);
    }
    

    So the resulting system is one where ClientHandler is responsible for all incoming messages and events, and ClientNotifier handles all outgoing stuff. These two classes are quite difficult to test as they contain a lot of other crap. Networking can't really be unit tested anyway, but the Connection object in these two classes can be mocked. The World library is entirely unit testable without any consideration of networking components, which is what I wanted to acheive in my design anyway.

    It's a big system, and I've not covered much of it here, but hope this can give you some hints. Ask me if you'd like any more specific information on it.

    If I could give any more advice, it would be to avoid any static or singletons - they'll come back to bite you. Favour increasing complexity if you need to as an alternative, but document where you need to. The system I have might seem complex to a maintainer, but is well documented. To a user it's particularly simple to use. Main is essentially

    var world = new World();
    var connectionFactory = new ClientConnectionFactory(world);
    var server = new Server(settings.LocalIP, settings.LocalPort, connectionFactory);
    

    这篇关于孩子对象到父母的通信模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆