我正在开发我的小游戏项目,作为学习和练习 C# 的一种方式,但我遇到了一个设计问题。假设我们有以下一组类:
interface IGameState
{
//Updates the state and returns next active state
//(Probably itself or a new one)
IGameState Tick();
}
class Game
{
public Game(IGameState initialState)
{
activeState = initialState;
}
public void Tick()
{
activeState = activeState.Tick();
}
IGameState activeState;
}
Game 基本上是 GameStates 的状态机。我们可以有MainMenuState,LoadingState或SinglePlayingState。但是添加MultiplayerState(代表玩多人游戏)需要一个套接字连接到服务器:
class MultiplayerState : IGameState, IDisposable
{
public IGameState Tick()
{
//Game logic...
//Communicate with the server using the Socket
//Game logic...
//Render the game
return this;//Or something else if the player quits
}
public void Dispose()
{
server.Dispose();
}
//Long-living, cannot be in method-scope
Socket server;//Or similar network resource
}
好吧,这是我的问题,我无法将它传递给它,Game因为它不知道它应该处理它,并且调用代码无法轻易知道游戏何时不再需要它。这个类设计几乎就是我到目前为止所实现的,我可以添加IDisposable,IGameState但我认为它不是一个好的设计选择,毕竟不是所有IGameState的都有资源。此外,这个状态机在某种意义上是动态的,任何活动IGameState都可以返回新状态。所以Game真的不知道哪些是一次性的,哪些是一次性的,所以它只需要测试一切。
所以这让我问了几个问题:
如果我有一个类对非密封类型的参数(例如initialState
在游戏的 ctor 中)声明所有权,我应该总是假设它可以IDisposable
吗?(可能不是)
如果我有一个IDisposable
实例,我是否应该通过强制转换到一个没有实现的基础来放弃它的所有权IDisposable
?(可能没有)
我从中收集到,IDisposable
感觉像是一个非常独特的界面,具有显着的有损(*) 语义 - 它关心自己的生命周期。这似乎与提供有保证但非确定性内存管理的 GC 本身的想法直接冲突。我来自 C++ 背景,所以真的感觉它试图实现 RAII 概念,但是一旦有 0 个引用,就会手动调用 Dispose(destructor)。我并不是说这是对 C# 的咆哮,更像是我错过了某些语言功能?或者也许是 C# 特定的模式?我知道有,using
但这只是方法范围。接下来是终结器,它可以确保调用 Dispose 但仍然不确定,还有其他的吗?也许像 C++' 这样的自动引用计数shared_ptr
?
正如我所说,上面的例子可以通过不同的设计解决(但我认为不应该),但没有回答可能不可能的情况,所以请不要过多关注它。理想情况下,我希望看到解决类似问题的一般模式。
(*) 对不起,也许不是一个好词。但我的意思是,很多接口都表达了一种行为,如果类实现了所述接口,它只会说“嘿,我也可以做这些事情,但如果你忽略我的那部分,我仍然可以正常工作”。遗忘IDisposable
不是无损的。我发现以下问题表明 IDisposable 通过组合传播,或者它可以通过继承传播。这对我来说似乎是正确的,需要更多的输入,但是可以。也正是这样MultiplayerState
被感染了。但是在我的Game
课堂示例中,它也想向上游传播,这感觉不对。
最后一个问题可能是是否应该有任何有损接口,比如它是否是适合这项工作的工具,在这种情况下是什么?或者我知道还有其他常用的有损接口吗?
MYYA
相关分类