【问题标题】:Maintaining modularity in Main()?在 Main() 中保持模块化?
【发布时间】:2012-04-07 20:11:09
【问题描述】:

我正在为家庭作业编写简单的纸牌游戏“战争”,现在该游戏可以运行,我正在尝试使其更加模块化和有条理。下面是Main() 的一部分,其中包含该程序的大部分内容。我应该提一下,该课程是用 C# 教授的,但它不是 C# 课程。相反,我们正在学习基本逻辑和 OOP 概念,因此我可能没有利用某些 C# 功能。

bool sameCard = true;

while (sameCard)
{
    sameCard = false;
    card1.setVal(random.Next(1,14));        // set card value
    val1 = determineFace(card1.getVal());   // assign 'face' cards accordingly
    suit = suitArr[random.Next(0,4)];       // choose suit string from array
    card1.setSuit(suit);                    // set card suit
    card2.setVal(random.Next(1,14));        // rinse, repeat for card2...
    val2 = determineFace(card2.getVal());    
    suit = suitArr[random.Next(0,4)];        
    card2.setSuit(suit);  

    // check if same card is drawn twice:

    catchDuplicate(ref card1, ref card2, ref sameCard); 
}
Console.WriteLine ("Player: {0} of {1}", val1, card1.getSuit());
Console.WriteLine ("Computer: {0} of {1}", val2, card2.getSuit());

// compare card values, display winner:

determineWinner(card1, card2);   

所以这是我的问题:

  • 我可以在 Main() 中使用循环并仍然认为它是模块化的吗?
  • 抽卡过程是否写得好/包含得当?
  • 在方法中打印消息是否被认为是不好的做法(即:determineWinner())?

我只编程了两个学期,我想在这个阶段养成良好的习惯。任何意见/建议将不胜感激。

编辑:

catchDuplicate() 现在是一个布尔方法,调用看起来像这样:

sameCard = catchDuplicate(card1, card2);

感谢@Douglas。

【问题讨论】:

  • 不确定这是否真的是 StackOverflow 问题。这是一个最佳实践问题,可能是主观的,但往往会达成共识。
  • catchDuplicate 是否曾经改变过card1card2 的值?如果没有,那么您应该更改其签名:sameCard = catchDuplicate(card1, card2);
  • @SionSheevok 哦,好吧,如果这应该被移动,我会非常乐意。
  • @Douglas 你是对的,它没有。很好的收获,谢谢!

标签: c# oop module program-structure


【解决方案1】:

我可以在 Main() 中使用循环并仍然认为它是模块化的吗?

是的,你可以。然而,OOP 程序中的Main 通常只包含少数启动核心功能的方法调用,然后将其存储在其他类中。

抽卡过程写得好/包含得当吗?

部分。如果我正确理解了您的代码(您只显示Main),您会采取一些行动,当以错误的顺序或错误的值完成时,可能不会有好的结果。可以这样想:如果您出售您的类库(不是整个产品,而只是您的类),那么对于没有经验的用户来说,使用您的类库最清晰的方法是什么?

即,考虑一个包含一副纸牌的类Deck。在创建时,它会创建所有卡片并将其洗牌。给它一个方法 Shuffle 来在你的类的用户需要洗牌时洗牌,并添加像 DrawCard 这样的方法来处理发牌。

进一步:您的方法不包含在它们自己的类中,但具有在类中更好的功能。即,determineFace 更适合作为 Card 类的方法(假设 card2 的类型为 Card)。

在方法中打印消息是否被认为是不好的做法(即:determineWinner())?

是的,不是的。如果您只想在测试期间显示消息,请使用Debug.WriteLine。在生产版本中,这些将是无操作的。但是,当您在生产版本中编写消息时,请确保从方法名称中可以清楚地看到这一点。即WriteWinnerToConsole 或其他东西。

这样做更常见,因为:您会打印什么格式的信息?应该附带什么文字?您如何处理本地化?但是,当您编写程序时,显然它必须包含将内容写入屏幕(或表单或网页)的方法。为此,它们通常包含在特定的类中。在这里,例如可以是 CardGameX 类。

一般想法
想想“一个方法/函数应该只有一个任务和一个任务,它不应该有副作用(比如计算正方形打印,然后打印是副作用)”的原则。

类的原则是,非常高级:一个类包含逻辑上属于一起并在同一组属性/字段上操作的方法。一个相反的例子:Shuffle 不应该是类Card 中的方法。但是,它在逻辑上属于 Deck 类。

【讨论】:

  • 我真的很喜欢这个答案。感谢您清晰而详细的解释!到目前为止,我对 OOP 的理解水平有限(我的班级刚刚走出程序领域),但这有很大帮助。
【解决方案2】:

如果你的家庭作业的主要问题是创建一个模块化应用程序,你必须将所有逻辑封装在专门的类中。 每个班级只能做一项工作。 使用卡片的函数必须属于卡片类。 抽卡的函数,应该是另一个类。

我认为这是你作业的目标,祝你好运!

【讨论】:

  • 通常,在“最佳实践”-OOP 中,每种方法只做一项工作。一个类包含一组相关的作业,这些作业对相同的数据(包含在字段/属性中)进行操作。
  • +1。是的,基本上Main 方法应该只实例化一个游戏类并启动游戏。类似var game = new CardsGame(); game.Run(); Console.ReadKey();
  • 由于问题被标记为作业,我们必须尝试向他展示路径,而不是牵着他的手走路径。现在我看到很多开发人员现在只遵循食谱,而不是编程。我对这个学徒有希望。
【解决方案3】:

对“最佳做法”的所有建议持保留态度。永远为自己着想。

也就是说:

  • 我可以在 Main() 中使用循环并仍然认为它是模块化的吗?

这两个概念是独立的。如果您的 Main() 只执行高级逻辑(即调用其他方法),那么它是否在循环中这样做并不重要,毕竟算法需要一个循环。 (你不会不必要地添加一个循环,不是吗?)

根据经验,如果可能/可行,请让您的程序自我记录。让它“可读”,这样,如果一个新人(甚至是几个月后的你)看到它,他们可以在任何层面上理解它。

  • 抽卡过程写得好/包含得当吗?

没有。首先,一张牌永远不应该被选中两次。对于更“模块化”的方法,我会有这样的东西:

while ( Deck.NumCards >= 2 )
{
   Card card1 = Deck.GetACard();
   Card card2 = Deck.GetACard();
   PrintSomeStuffAboutACard( GetWinner( card1, card2 ) );
}
  • 在方法(即:defineWinner())中打印消息是否被认为是不好的做法?

determineWinner 的目的是打印消息吗?如果答案是“否”,那么这不是“坏习惯”的问题,你的功能是完全错误的。

也就是说,存在“调试”构建和“发布”构建之类的东西。为了帮助您调试应用程序并确定哪些有效,哪些无效,添加日志消息是一个好主意。

确保它们是相关的,并且它们没有在“发布”构建中执行。

【讨论】:

  • 感谢您的回答!您对该方法目的的解释澄清了事情。我之前已经添加了日志消息,以查看程序中发生了什么,不知道这是实际做法哈哈。
  • +1 表示“使代码可读”。 @nvillec:这些以及更多最佳实践可以在非常易读和易于访问的书The Pragmatic Programmer 中找到。我可以向您强烈推荐的里程碑(不要看发布日期,所有内容仍然适用)。
【解决方案4】:

问:我可以在 Main() 中使用循环并仍然认为它是模块化的吗?

答:是的,您可以使用循环,这对模块化并没有真正的影响。

问:抽卡过程写得好/包含得当吗?

A:如果你想更加模块化,把 DrawCard 变成一个函数/方法。也许只是写 DrawCards 而不是 DrawCard,但是那里有一个优化与模块化的问题。

问:在方法中打印消息是否被认为是不好的做法(即:determineWinner())?

A:我不会说在方法中打印消息是不好的做法,它只取决于上下文。理想情况下,游戏本身只处理游戏逻辑。程序可以有某种游戏对象,它可以从游戏对象中读取状态。这样,您可以在技术上将游戏从基于文本的游戏更改为图形游戏。我的意思是,这对于模块化来说是理想的,但考虑到最后期限,它可能并不实用。您总是必须决定何时必须牺牲最佳实践,因为没有足够的时间。可悲的是,这种情况经常发生。

将游戏逻辑与其呈现分开。像这样一个简单的游戏,它是一个不必要的依赖。

【讨论】:

  • +1 获取详细答案。感谢您的深入解释!我有一段时间有一个drawCard 方法,其中包含花色和价值分配,并为每张牌调用它。但是,理想情况下,每种方法都应该只负责一项任务,对吧?画线在哪里,是“绘制卡片”一项任务还是个别set 方法是任务。我想我还不太明白是什么定义了一个任务。
猜你喜欢
  • 1970-01-01
  • 2019-01-18
  • 2012-03-16
  • 2021-10-25
  • 1970-01-01
  • 2014-02-15
  • 2021-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多