samedi 5 juin 2021

Best practices: Exposing/passing member objects down vs. chained methods passing a call up

Suppose we're designing a game engine. Within this game engine, there's a class that represents the whole game (Game). When initialized, it creates various resources that should be unique to an instance of the game (audio, renderer, window, etc.) which includes a container to manage controllers (ControllerList) as well as an object representing the game world (GameWorld). The GameWorld then maintains a set of game entities (Entity).

One Entity will represent the player, which means it needs to have access to a controller object so it knows how to update itself. The controller object is a few layers up and inside a container object (the ControllerList), so my question is this: How should the game entity be provided with information about the controller state? There seems to be several options that would work, but I'm not sure which one would be considered better under object-oriented best practices.

  1. Game has an accessor that provides a reference to ControllerList and that reference is passed down through GameWorld to the Entity that needs it. This works, but it breaks encapsulation because we're directly sharing a reference to an internal object so other objects can access it. (Maybe it's okay because these other objects also exist within the Game object?)
  2. Game exposes various methods for interacting with ControllerList, such as getting the number of controllers and getting the state of each one, without actually exposing the object itself. A handle to the Game is passed down to the entity so that it can access these methods. This is pretty simple to implement, but also exposes entities to all the methods Game exposes, some of which an entity shouldn't know about (like the run() method that starts the game, which shouldn't be called twice).
  3. Game exposes various methods for interacting with ControllerList, such as getting the number of controllers and getting the state of each one, without actually exposing the object itself. The GameWorld then exposes the same methods that simply call the ones in Game. We duplicate these methods down to the Entity level to give an entity access to the ControllerList. This way, each layer only needs a handle to the one above it to call the appropriate methods in the layer above. This allows us to only provide methods we want to expose at each layer, but also means having to write out several methods that only serve to pass the call up to the next layer.
  4. ControllerList is implemented using the Service Locator pattern and stored statically in a base class. Game would hook up the service during initialization and be responsible for updating the state of the ControllerList. The player Entity would inherit from this base class to get access to the service, thereby bypassing the need to pass anything through the layers.

Although the example given is a game engine, this problem could easily exist in any object that's composed of multiple objects and containers. I'm curious about which approach, if any, would be considered "best practice".

Aucun commentaire:

Enregistrer un commentaire