我们说我写下面的代码:
游戏模块
module Game where
import Player
import Card
data Game = Game {p1 :: Player,
p2 :: Player,
isP1sTurn :: Bool
turnsLeft :: Int
}
播放器模块
module Player where
import Card
data Player = Player {score :: Int,
hand :: [Card],
deck :: [Card]
}
和卡片模块
module Card where
data Card = Card {name :: String, scoreValue :: Int}
然后我写了一些代码来实现逻辑,玩家轮流从他们手中抽取牌和牌来为他们的分数添加奖金,直到游戏结束。
然而,我意识到完成这段代码后,我写的游戏模块很无聊!
我想重构纸牌游戏,所以当你玩牌时,而不是仅仅添加一个分数,而不是卡片任意改变游戏。
因此,我将Card
模块更改为以下
module Card where
import Game
data Card = Card {name :: String,
onPlayFunction :: (Game -> Game)
scoreValue :: Int}
当然这会使模块导入形成一个循环。
如何解决此问题?
琐碎的解决方案:
将所有文件移动到同一模块。这很好地解决了这个问题,但减少了模块化;我不能再为同一个游戏重复使用相同的卡片模块。
模块维护解决方案:
将类型参数添加到Card
:
module Card where
data Card a = {name :: String, onPlayFunc :: (a -> a), scoreValue :: Int}
将另一个参数添加到Player
:
module Player where
data Player a {score :: Int, hand :: [card a], deck :: [card a]}
对Game
进行最后修改:
module Game where
data Game = Game {p1 :: Player Game,
p2 :: Player Game,
}
这保持了模块化,但需要我为我的数据类型添加参数。如果数据结构更深层次嵌套,我可能需要为我的数据添加很多参数,如果我不得不将这个方法用于多个解决方案,我最终可能会得到大量的类型修饰符。
那么,有没有其他有用的解决方案来解决这个重构,或者这些只是两个选项?
答案 0 :(得分:7)
您的解决方案(添加类型参数)并不错。您的类型变得更加通用(如果需要,可以使用Card OtherGame
),但如果您不喜欢额外的参数,您可以:
CardGame
,并将该模块导入其他模块,或者ghc
中,使用{-# SOURCE #-}
pragma到break the circular dependency 最后一个解决方案需要在Card.hs-boot
中编写带有类型声明子集的Card.hs
文件。