将模板方法与策略相结合


14

我的软件工程课上的一项作业是设计一个可以玩不同形式的特定应用程序的应用程序。有问题的游戏是Mancala,其中一些游戏称为Wari或Kalah。这些游戏在某些方面有所不同,但对于我来说,重要的是要知道这些游戏可能在以下方面有所不同:

  • 移动结果的处理方式
  • 确定游戏结束的方式
  • 确定获奖者的方式

设计该功能的第一件事是使用策略模式,我在算法(游戏的实际规则)上有所不同。设计看起来像这样: 在此处输入图片说明

然后我对自己想,在Mancala和Wari的游戏中,确定获胜者的方式是完全相同的,并且代码会重复。我不认为这从定义上讲违反了“一条规则,一个地方”或DRY原则,因为Mancala规则的变化不会自动意味着在Wari中也应更改规则。然而,从我教授的反馈中,我印象深刻地找到了另一种设计。

然后我想到了这个: 在此处输入图片说明

每个游戏(Mancala,Wari,Kalah等)都将具有每个规则界面类型的属性,即WinnerDeterminer,如果有Mancala 2.0版本与Mancala 1.0相同,除了如何确定获胜者之外,使用Mancala版本。

我认为将这些规则作为策略模式实施当然是有效的。但是,当我想进一步设计时,真正的问题就来了。

在阅读有关模板方法模式的信息时,我立即认为它可以应用于此问题。用户进行移动时所执行的操作始终相同,并且顺序相同,即:

  • 在洞中放石头(所有游戏都一样,因此将在模板方法本身中实现)
  • 确定举动的结果
  • 确定游戏是否由于上一步而结束
  • 如果游戏结束,确定谁赢了

最后三个步骤全部属于上述我的策略模式。合并这两项时,我遇到很多麻烦。我发现一种可能的解决方案是放弃策略模式并执行以下操作: 在此处输入图片说明

我真的没有看到策略模式与此之间的设计差异吗?但是我确定我需要使用模板方法(尽管我可以肯定必须使用策略模式)。

我也无法确定谁将负责创建TurnTemplate对象,但是使用策略模式,我觉得我拥有对象族(三个规则),可以使用抽象工厂模式轻松创建对象族。然后MancalaRuleFactory,我将拥有一个,WariRuleFactory等等,他们将创建规则的正确实例并将其交给我RuleSet

假设我使用了策略+抽象工厂模式,并且我有一个RuleSet对象,其中包含用于这三个规则的算法。我觉得仍然可以使用模板方法模式的唯一方法是将此RuleSet对象传递给我TurnTemplate。然后出现的“问题”是,我永远不需要的具体实现TurnTemplate,这些类将变得过时。在我受保护的方法中,TurnTemplate我可以调用ruleSet.determineWinner()。结果,TurnTemplate该类将不再是抽象的,而是必须变得具体,那么它仍然是模板方法模式吗?

总而言之,我是在以正确的方式思考还是在错过简单的东西?如果我处在正确的轨道上,如何将策略模式和模板方法模式结合在一起?


1
我想说的是您的想法很重要。如果您从教授那里获得的反馈没有证实这一点,请让他/她指出缺陷,或提示他在回答中将要采取的措施。停止尝试读思想。假设您的教授(以及后来的用户)想要的东西可能是您最糟糕的事情。
Marjan Venema 2012年

...在Mancala和Wari的游戏中,确定获胜者的方式是完全相同的,并且代码将重复。 ”为什么这意味着代码是重复的?只要让两个类都调用相同的函数即可。
D Drmmr

Answers:


6

看完您的设计后,您的第一次和第三次迭代似乎都是更优雅的设计。但是,您提到您是一名学生,而您的教授给了您一些反馈。在不确切知道您的作业或班级目的是什么,或者不知道您的教授所提出的建议的更多信息的情况下,我想在下面说些什么。

在第一个设计中,您声明自己RuleInterface是一个接口,该接口定义如何处理每个玩家的回合,如何确定游戏是否结束以及如何在游戏结束后确定获胜者。似乎这是经历变化的一系列游戏的有效接口。但是,取决于游戏,您可能有重复的代码。我同意改变一个游戏规则的灵活性是一件好事,但是我也认为代码重复对于缺陷很可怕。如果您在实现之间复制/粘贴有缺陷的代码,并且其中有一个错误,则现在您需要在不同位置修复多个错误。如果您在不同的时间重写实现,则可能会在不同的位置引入缺陷。这些都不是可取的。

您的第二个设计看起来很复杂,带有一棵深厚的继承树。至少,它比解决这种类型的问题要深得多。您还将开始将实现细节分解为其他类。最终,您正在建模和实现游戏。如果您需要混合使用规则来确定移动,比赛结束和获胜者的规则,那么这可能是一种有趣的方法,这似乎与您提到的要求不符。您的游戏是定义明确的规则集,我会尽量将游戏封装到单独的实体中。

您的第三个设计是我最喜欢的设计。我唯一关心的是它没有处于正确的抽象级别。现在,您似乎正在建模转弯。我建议考虑设计游戏。考虑到您有一些正在用石头在棋盘上移动的球员。您的游戏需要这些演员在场。从那里开始,您的算法不是,doTurn()而是playGame(),从最初的移动到最终的移动,然后终止。在每个玩家移动之后,它会调整游戏状态,确定游戏是否处于最终状态,如果是,则确定获胜者。

我建议仔细研究一下您的第一个和第三个设计,并与他们合作。考虑原型可能也有帮助。使用这些接口的客户端会是什么样?一种设计方法对于实现实际上要实例化游戏并玩游戏的客户端是否更有意义?您需要意识到它正在与之交互。在您的特定情况下,它是Game类以及任何其他相关元素-您不能孤立地设计。


既然您提到您是学生,所以我想分享一下我担任软件设计课程助教时的一些事情:

  • 模式只是捕获过去使用过的东西的一种方式,但是将它们抽象到可以在其他设计中使用的地步。每个设计模式目录都会给模式起一个名称,说明其用途,用途以及最终会限制设计的情况。
  • 设计带有经验。擅长设计的最佳方法不是简单地专注于建模方面,而是要了解实现该模型所要考虑的内容。如果无法轻松实现或不适合大型系统设计或其他系统设计,则最优雅的设计很有用。
  • 很少有设计是“正确”或“错误”的。只要设计满足系统要求,就不会错。一旦从每个需求映射到系统将如何满足该需求的某种表示形式,设计就不会错。在这点上,关于灵活性,可重用性,可测试性或可维护性等概念仅是定性的。

感谢您的出色建议,尤其是您对错误抽象级别这一事实的观点。我现在将GameTemplate其更改为确实感觉好很多的。这也让我的工厂方法相结合来初始化播放器,电路板等
Mekswoll

3

你的困惑是有道理的。关键是模式不是相互排斥的。

模板方法是其他一系列模式(例如策略和状态)的基础。本质上,策略接口包含一个或多个模板方法,每个方法都要求实现策略的所有对象都具有(至少)类似doAction()方法的内容。这使得这些策略可以互相替代。

在Java中,接口不过是一组模板方法。同样,任何抽象方法本质上都是模板方法。这种模式(除其他外)对于语言的设计者是众所周知的,因此他们将其内置。

@ThomasOwens为解决您的特定问题提供了极好的建议。


0

如果您被设计模式所困扰,我的建议是先对游戏进行原型设计,然后设计模式就应该出现在您眼前。我认为先尝试完美地设计一个系统然后实施它真的是不可能或不建议的(以类似的方式,当人们尝试先编写整个程序然后进行编译,而不是一点一点地去做时,我发现它很困惑。 。问题是,您不太可能会想到逻辑必须处理的每种情况,并且在实施阶段,您可能会失去所有希望,或者尝试坚持最初的有缺陷的设计并引入hack,甚至更糟糕的是什么也没有提供。


2
虽然总体上我同意您的看法,但这并不是解决OP的问题的真正答案。“不要那样做”是一个答案,但如果不能提供可行的替代方案,那就是一个很差的答案。
扬尼斯2012年

@YannisRizos虽然我也同意你的观点,但我仍然认为他给出了合理的建议(如果建议不是正确的话,也可以提供见解)。因此,+ 1。即使,是的,从技术上讲,这不是一个非常有用的答案。
购买777

-1

让我们开始讨论。绝对不需要任何游戏接口,设计模式,抽象类和UML。

如果您有相当数量的支持类,如UI,模拟等,那么基本上所有非特定于游戏逻辑的代码都将被重用。而且,您的用户不会动态更改其游戏。您不会在游戏之间以30Hz的频率翻转。您玩一个游戏大约半小时。因此,您的“动态”多态性实际上根本不是动态的。这是静态的。

因此,明智的选择是使用通用的函数抽象,例如C#Action或C ++ std::function,创建一个Mancala,一个Wari和一个Kalah类,然后从那里开始。

std::unordered_map<std::string, std::function<void()>> games = {
    { "Kalah", [] { return Kalah(); } },
    { "Mancala", [] { return Mancala(); } },
    { "Wari", [] { return Wari(); } }
};
void play() {
    std::function<void()> f;
    f = games[GetChoiceFromUI()];
    f();
    if (PlayAgain()) return play();
}
int main() {
    play();
}

做完了

你不叫游戏。游戏打给你。


3
我完全看不到这有什么帮助。问这个问题的人是一名学生。他明确询问了设计原则以及两个设计模式之间的相互作用。说在工业中的某些情况下不需要设计模式或UML可能是正确的,但是考虑设计模型,设计模式和UML可能是所要练习的重点。
Thomas Owens

3
关于它们,无话可说,因为它们无处适用。没有时间花在思考如何使用设计模式完全完成设计上,除非您打算学习如何不使用它们。
DeadMG

4
在SoftDev中,有一种令人担忧/烦恼的趋势,其中使用的设计模式被认为比解决问题更重要。存在过度工程的情况。
詹姆斯

2
@DeadMG:虽然你们两个基本上都是对的,但在OP的情况下,问题是通过考试,而解决该问题的方法是使用设计模式和UML。无论我们多么正确,如果他的教授不同意他的解决方案,他就不会通过。
Goran Jovic 2012年

2
我的整个生活中,我一直认为这是“黄铜税” ......哇,“基本事实”的方式,使更多的意义。大声笑
购买了777
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.