2010-04-02 27 views
1

我正在写一个有很多组件的游戏。其中许多是相互依赖的。在创建它们时,我经常会遇到catch-22情况,比如“WorldState的构造函数需要一个PathPlanner,但是PathPlanner的构造函数需要WorldState”。具有相互依赖性的组件的初始化 - 可能的反模式?

最初,这个问题不大,因为引用所需的所有东西都在GameEngine左右,并且GameEngine被传递到了所有东西。但我不喜欢这种感觉,因为它感觉我们对不同组件的访问权限过大,使得实施边界变得更加困难。

这里是有问题的代码:

/// <summary> 
    /// Constructor to create a new instance of our game. 
    /// </summary> 
    public GameEngine() 
    { 
     graphics = new GraphicsDeviceManager(this); 
     Components.Add(new GamerServicesComponent(this)); 

     //Sets dimensions of the game window 
     graphics.PreferredBackBufferWidth = 800; 
     graphics.PreferredBackBufferHeight = 600; 
     graphics.ApplyChanges(); 

     IsMouseVisible = true; 
     screenManager = new ScreenManager(this); 

     //Adds ScreenManager as a component, making all of its calls done automatically 
     Components.Add(screenManager); 

     // Tell the program to load all files relative to the "Content" directory. 
     Assets = new CachedContentLoader(this, "Content"); 

     inputReader = new UserInputReader(Constants.DEFAULT_KEY_MAPPING); 

     collisionRecorder = new CollisionRecorder(); 

     WorldState = new WorldState(new ReadWriteXML(), Constants.CONFIG_URI, this, contactReporter); 

     worldQueryUtils = new WorldQueryUtils(worldQuery, WorldState.PhysicsWorld); 

     ContactReporter contactReporter = new ContactReporter(collisionRecorder, worldQuery, worldQueryUtils); 

     gameObjectManager = new GameObjectManager(WorldState, assets, inputReader, pathPlanner); 
     worldQuery = new DefaultWorldQueryEngine(collisionRecorder, gameObjectManager.Controllers); 
     gameObjectManager.WorldQueryEngine = worldQuery; 

     pathPlanner = new PathPlanner(this, worldQueryUtils, WorldQuery); 

     gameObjectManager.PathPlanner = pathPlanner; 

     combatEngine = new CombatEngine(worldQuery, new Random()); 
    } 

这里是上面说的有问题的摘录:

 gameObjectManager = new GameObjectManager(WorldState, assets, inputReader, pathPlanner); 
     worldQuery = new DefaultWorldQueryEngine(collisionRecorder, gameObjectManager.Controllers); 
     gameObjectManager.WorldQueryEngine = worldQuery; 

我希望没有人会忘记设置gameObjectManager.WorldQueryEngine是,否则会失败。问题如下:gameObjectManager需要WorldQuery,而WorldQuery需要gameObjectManager的属性。

而且,除了这个问题,这是一个可维护性的混乱,因为如果构造函数没有以特定顺序调用,程序将崩溃。

我该怎么办?我有没有发现反模式?

回答

5

圆形类的依赖不是总是设计错误,但在这种情况下,我会说你要求麻烦。

问题似乎是,您需要此GameObjectManager中的Controllers来初始化GameObjectManager的某些其他依赖项。但为什么GameObjectManager必须是创建控制器的人?如果有不止一件事情依赖于它们,那么你可能应该有一个类似于ControllerFactory的东西,它允许你分别初始化该依赖关系,并通过构造函数将它传递给GameObjectManager

用难以置信的通用名称GameObjectManager来判断,我想说你在这里处理的是God Object。至少你有Sequential Coupling,这不是一件好事。

至于你能做些什么,很难说如果不知道GameObjectManager究竟是做什么的,但是你应该尝试去做上面提到的事情 - 还有其他的东西负责创建“控制器”并提供这些作为依赖到GameObjectManager,而不是让那个单一的对象成为负责创建它们的人。

+0

好吧,这对我发布的摘录很好,因为'GameObjectManager'本身不是依赖项,但它的一个属性是。但是当两个对象需要彼此的实例并且不能在没有它的情况下实例化时呢? (所以,如果所有'GameObjectManager'都是必需的,而不仅仅是'GameObjectManager.Controllers') – 2010-04-02 16:51:46

+0

@Rosarch:你指的是一个循环依赖。通常,这被认为是设计错误,但递归数据结构和辅助对象的明显例外仅依赖于负责创建和管理它们的相同对象(如资源池)。在其他情况下,当你有一个循环依赖时,通常在一个或两个依赖关系中有一部分功能可以被重构到它自己的类中,这样就不必让两个类相互依赖,而是依赖于第三(新)班。 – Aaronaught 2010-04-02 17:07:07

+0

我还想指出的是,在我的游戏术语中,“游戏对象”是指世界上的一个实体,如树或玩家。这并不意味着OOP意义上的对象。 – 2010-04-05 21:12:08

0

如下您可以创建一个上下文对象:

  • 你定义一个工厂接口
  • 你定义一个关键的唯一标识每个组件
  • 对于每一个组件您注册的实现工厂界面(创建组件)和上下文。
  • 各组分的构造将收到类型上下文的单个参数
  • 构造内的第一个语句将注册到上下文对象
  • 当构造需要初始化该组件(使用适当的密钥)一个带有另一个组件的字段,它将通过指定需要组件的密钥从上下文对象中获取该组件。
  • 当上下文收到获取(密钥)消息时,它将检查一个组件是否已经在该密钥下注册。如果是这样,它会返回它。否则它将调用在此项下注册的工厂对象。

这种模式在Sun的Javac编译器的实现中被广泛使用。你可以看一下它here(Context类的代码)和here(占编译器组件之一的代码。特别是,看看构造函数)

另一种方法是,你移动到Dependency Injection container其将为您解决这些依赖关系。