简体   繁体   English

“静态类”还是传递引用?

[英]“static class” or pass references around?

I'm part of a team designing the server for a client/server model naval warfare video game (University course). 我是为客户/服务器模型海战视频游戏(大学课程)设计服务器的团队的成员。 We have a fairly concrete (well, I think) system design, but there is one aspect that bothers me. 我们有一个相当具体(我认为是很好)的系统设计,但是有一个方面困扰着我。

The basic layout of the system is: 系统的基本布局是:

Server [thread] (handles incoming connections)
|
Game [thread] (deals with game events in it's work queue and sending messages to clients)
|--Environment (holds environment variables, deals with collision)
|--Client(s) [thread] (handles incoming messages from client sockets and adds events to the Game's work queue)
    |--Ship (holds game data, eg: hit points, fire power, etc)
    |--Parser (parses client messages and creates game event objects)
|--GameEvent (event objects held in a queue that can preform appropriate work and send appropriate responses to clients)

Now, my issue is that both Client and GameEvent (and probably Environment, once we get to it) need a reference to the Game object that they belong to. 现在,我的问题是,Client和GameEvent(以及环境,一旦我们了解到它)都需要引用它们所属的Game对象。

Clients need to add GameEvent's to the Game's work queue. 客户需要将GameEvent添加到游戏的工作队列中。

GameEvent's need to access other game data (other Client's Ships, Environment). GameEvent需要访问其他游戏数据(其他客户的飞船,环境)。

Is there a better/more conventional method instead of having these objects store a local reference to their Game? 是否有更好/更常规的方法代替让这些对象存储对其游戏的本地引用? What about declaring all of Game's methods as static? 将Game的所有方法都声明为静态方法怎么样? We only need to handle one game at a time, so there wouldn't ever be more than one instance of Game... 我们一次只需要处理一个游戏,因此就不会有一个以上的Game实例。

I'm sure there is a convention for a system with one central object that has many helper objects that need to reference it. 我确定对于具有一个中央对象的系统有一个约定,该中央对象具有许多需要引用它的辅助对象。

Did you consider using a Dependency Injection framework like Guice? 您是否考虑过使用像Guice这样的依赖注入框架? There you have config classes called "modules", where you bind your interface Game to an implementation (you can decide if you want a singleton or new instances). 那里有称为“模块”的配置类,您在其中将接口Game绑定到实现(您可以决定要单例还是新实例)。 A Client class would look like 客户端类看起来像

public class Client {
   private final Game game;

   @Inject
   public Client(Game game) {
      this.game = game; 
   }   

   ... 
}

You can construct this class as usual, providing a Game instance (eg for testing, using a mock Game class). 可以照常构造此类,提供Game实例(例如,使用模拟Game类进行测试)。 But if you let Guice create this instance for you (which doesn't need to be directly, it works as well if another class injects Client), you get automatically the instance specified in your Guice module. 但是,如果让Guice为您创建此实例(不需要直接创建该实例,则在另一个类注入Client的情况下也可以正常工作),您会自动获得在Guice模块中指定的实例。

I know it takes some time to wrap your head around that concept, but I can confirm that this leads to cleaner, more flexible code. 我知道需要花费一些时间来解决这个概念,但是我可以确认这可以使代码更简洁,更灵活。

If there's really only logically ever one instance, you can use a singleton. 如果真的只有逻辑永远一个实例,你可以使用一个单例。 The canonical form of a singleton is: 单例的规范形式为:

public enum Singleton {
    INSTANCE;

    // fields and methods here
}

That way, you don't have to shoehorn everything into static methods (though, if you want to write static methods that reference INSTANCE , that's fine too). 这样,您不必将所有内容都塞进静态方法中(不过,如果您要编写引用INSTANCE静态方法,也可以)。 Any code that wants to access the singleton just uses Singleton.INSTANCE , and you don't have to pass it around if you don't want to. 任何想要访问单例的代码都只使用Singleton.INSTANCE ,并且如果不需要的话,也不必传递它。

Passing the reference around will keep your options open, it can still be a reference to an actual static object. 传递引用将使您的选项保持打开状态,它仍然可以是对实际静态对象的引用。 Also the concept of a request context might be useful, an object that holds all references needed to process a single request, and you pass that around. 请求上下文的概念也可能有用,该对象包含处理单个请求所需的所有引用,然后将其传递出去。

Check out Inversion of Control (IOC) and containers. 检出控制反转(IOC)和容器。

That way, in your Client and GameEvent classes, whenever you need access to the Game, you just do something like: 这样,在Client和GameEvent类中,只要需要访问游戏,就可以执行以下操作:

var game = IoC.Resolve<Game>(); 

And then use the game instance methods... 然后使用游戏实例方法...

There's nothing wrong with passing references to a constructor and storing them. 将引用传递给构造函数并存储它们没有错。 You should also introduce an interface that will mediate access between your Game and your client and environment objects. 您还应该引入一个接口,该接口可以介导游戏与客户端和环境对象之间的访问。

I would strongly advise not using a singleton or a static class in your design. 我强烈建议不要在设计中使用单例或静态类。 There are lots of reasons for this, but the one that will probably affect you most immediately is that it makes things very hard to test. 造成这种情况的原因很多,但是可能最直接影响您的原因是它使测试变得非常困难。 At testing time, there will probably be more than one instance of Game. 在测试时,可能会有不止一个Game实例。

A common convention to ameliorate having one big central object with lots of helper objects is to try to avoid one big central object. 改善具有多个辅助对象的一个​​大型中心对象的常见约定是尝试避免一个大型中心对象。 You note that there are different clients of 'Game' with different needs. 您注意到,“游戏”的不同客户有不同的需求。 Maybe your interface on Game is too wide. 也许您在Game上的界面太宽了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM