发布日期: 3/18/2005更新日期: 3/18/2005Pratap Lakshman
Microsoft Corporation
适用于:
Microsoft Visual J# .NET
Visual Studio .NET
摘要:本文考察了可以在 Microsoft Visual J# .NET 中使用的设计解决方案。这是讨论如何通过 J# 解决问题的文章系列(包括三个部分)的第一部分。
![](/images/load.gif)
![](/images/load.gif)
![](/images/load.gif)
![](/images/load.gif)
![](/images/load.gif)
![](/images/load.gif)
![](/images/load.gif)
![](/images/load.gif)
![](/images/load.gif)
在这一包括三个部分的文章系列中,我们将介绍如何通过 J# 解决问题。尽管这些问题可能类似于玩具问题,但我们希望您能发现用于解决这些问题的思维过程对您有所帮助。这些问题足够简单,因而不会掩盖相应的解决方案。解决方案没有使用任何编程技巧,并且如果您选择用不同的语言实现所得到的设计,则可以重新使用这些设计。
该文章系列中的另外两个部分为第二部分 Delegates in Action with J# 和第三部分 Solving Constraints with J#。
如果您有特定的问题或反馈,请通过 jsfeedbk@microsoft.com 向我们发送电子邮件。我们期待着您的反馈。
简介“设计”是上下文驱动的。对于产品而言,该上下文是指它的用途、它的生存期、使用的材料、时间因素、使用模式以及规模等等。单人航空器的设计不会适合于航天飞机。汽船的设计不会适合于航空母舰。
软件系统设计没有什么不同。我们必须考虑各种问题,例如(仅举数例):
•
•
•
•
鉴于此,良好的设计通常具有下列特征:
•
•
•
•
•
我们在进行设计时必须考虑实现。
我们将采用一个示例问题并尝试说明上述问题。
该教程在多个完整的解决方案基础之上构建了一个示例,并且在每个步骤都通过应用简单的模式 [2] 来对其进行改进;在此过程中我们将说明“将接口作为类型”、“工厂”、“策略”等模式。
![](/images/load.gif)
![](/images/load.gif)
我们按如下方式描述骰子:
•
也就是说,我们完全按照我们可以执行的操作(“掷”)来描述骰子。
我们在 app.jsl 中实现了该解决方案。
清单 1.
![](/images/load.gif)
•
•
•
•
•
•
我们需要更好地表示骰子。
我们按如下方式描述骰子:
•
•
•
•
另外,让我们消除对骰子可以具有的面数的限制。我们将把它参数化。
dice.jsl 中实现了该骰子。
应用程序 app.jsl 中使用了该骰子。
清单 2.
![](/images/load.gif)
我们的上一个解决方案允许我们具有一个以上的骰子。
在继续讨论之前,让我们引入客户端和服务器的概念。“服务器”提供某种服务。在这种情况下,dice.jsl 提供骰子服务。“客户端”为 app.jsl,它利用该服务。
客户端和服务器之间的“耦合”是怎样的?客户端与它使用的骰子之间的联系有多紧密?如果我们要对骰子进行一些更改(例如,引入一些新的数据/方法),则会发生什么情况?客户端需要重新编译吗?
那样合理吗?如果在多个文件中使用该骰子,那么会怎样呢?如果它由多个客户端使用,那么会怎样呢?
将接口作为类型
我们在 rollable.jsl 中引入了骰子接口。dice.jsl 中的骰子类实现了该接口。客户端 app.jsl 只根据该接口工作。
清单 3.
您是否看到了我们可以改进的其他领域?
![](/images/load.gif)
我们的上一个解决方案引起了与客户端和服务器之间的耦合有关的问题。
我们的设计必须支持客户端和服务器的独立演变。
因此,我们需要将它们之间的耦合降低到最低程度。
像当前所实现的那样,骰子的实现对于客户端而言是“可见”的。这意味着当我们下一次对骰子类进行更改时,客户端需要重新编译。在客户端和服务器之间没有明显的分离。
让我们将客户端和服务器加以明显的分离。
我们创建了一个新的类 — dicefactory.jsl — 来管理骰子对象的创建。客户端现在只根据该工厂进行处理。这还为服务器提供了管理骰子的存储的灵活性。客户端不再需要知道以下信息:骰子位于何处?骰子是否是在堆上分配的?骰子是否是从预先分配的“骰子”集合中分配的?等等。
现在,下列文件提供了骰子服务(假设生成为 dice.dll)。
dice.dll)。
骰子的“实际”表示对于客户端而言不再是(而且也不需要是)“可见”的。客户端和服务器之间的耦合被限制为 dicefactory.jsl 和接口 rollable.jsl。
我们已经将客户端与服务器隔离!
清单 4.
![](/images/load.gif)
请记住,我们是在游戏方案中使用骰子 — 由人与计算机进行赌博,或者像在赌场中一样。
没有任何赌场喜欢输。因此,要求之一是使掷骰子具有可预测的结果。这一般称为“加载”骰子。如果赌场开始输了,则它们会切换到“已加载”的骰子。然后,它们就可以打败您,并开始赚钱。
正常情况下,骰子具有“随机性”的加载;也就是说,结果将是骰子各个面上的数字范围中的随机数。
测试
游戏开发人员如何测试该游戏?如果掷骰子的结果是随机的,则很难编写测试用例(尽管在该示例中,结果将位于固定范围中)。我们必须能够确保得到可预测的结果,以便有效地测试我们的软件。
策略
我们必须能够改变“加载”策略,而无须重新生成我们的骰子组件。(我们甚至可能不具有相应的源代码!)无论是从灵活性还是测试角度而言,能够在运行时改变掷(“加载”)骰子的策略都是值得的。
解决方案
我们在 rollstrategy.jsl 中将掷骰子抽象为一个接口。每个骰子都通过该接口引用 RollStrategy 对象的实例。我们将“加载”操作抽象为该骰子接口上的一个方法,该方法使我们可以设置要由骰子使用的 RollStrategy。该对象表示要在掷骰子时使用的策略。
我们在 dice.jsl 中对骰子表示进行增强。我们将 Roll 方法移到 app.jsl 外面,并且创建了 RandomRoll 类 (randomroll.jsl),以表示随机分布策略。这是骰子使用的默认策略。
我们在 rollable.jsl 中引入了用于加载骰子的方法,并且在骰子类 dice.jsl 中实现了它。Roll 函数现在使用该“加载”执行掷骰子操作。我们更新了 dicefactory.jsl 以便在创建时设置骰子的默认加载。
我们在 cyclicroll.jsl 中以类似的方式创建了 CyclicRoll 策略。
现在,客户端将初始化所需的掷骰子策略,在骰子上设置该策略,然后掷骰子。请注意我们如何在运行时改变它。
服务器文件(假设生成为 dice.dll)。
清单 5.
客户端文件(引用 dice.dll)。
清单 6.
只有 dicefactory.jsl、rollable.jsl、rollstrategy.jsl 对于客户端而言是“可见”的。
该实现具有下列特征:
•
•
•
•
•
•
完毕。
这样就完成了我们的“骰子设计”项目。
希望您能够在此过程中学习到一些知识!
![](/images/load.gif)
•
Rollable 接口不同,我们可以具有一个表示骰子的抽象类,并且让骰子的所有具体实现都从该抽象类继承。请联系我们所选择的实现,讨论这样做的优点。
•
参考
[1] Knuth, Donald, E., Selected Papers on Computer Science, Cambridge University Press, 1996
[2] Design Patterns: Elements of Reusable Object-Oriented Software—Gamma E. et al. Addison Wesley, 1995
转到原英文页面