at-m42:casestudies:cs04
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
at-m42:casestudies:cs04 [2009/04/09 17:11] – eechris | at-m42:casestudies:cs04 [2009/04/15 10:05] – eechris | ||
---|---|---|---|
Line 8: | Line 8: | ||
The adventure game first appeared in [[at-m42: | The adventure game first appeared in [[at-m42: | ||
- | In the first two iterations of this case study, we revisit the same case study and use class inheritance to model not just items with a name, description and values, but items in general. As with the earlier versions, we use containers to help model the relationships established between objects. Similarly we continue to make use of unit tests. In the [[third iteration]], | + | In the first two iterations of this case study, we revisit the same case study and use class inheritance to model not just items with a name, description and values, but items in general. As with the earlier versions, we use containers to help model the relationships established between objects. Similarly we continue to make use of unit tests. In the [[# |
An index to the source code for all the examples in this case study is [[/ | An index to the source code for all the examples in this case study is [[/ | ||
Line 37: | Line 37: | ||
< | < | ||
< | < | ||
- | < | + | < |
< | < | ||
- | < | + | < |
</ul> | </ul> | ||
</td> | </td> | ||
Line 46: | Line 46: | ||
</ | </ | ||
- | ===== Iteration I: An Initial Model ===== | + | ===== Iteration I: Confirm the Polymorphic Effect |
- | <code groovy 1| Initial object configuration | + | The specification mentions two kinds of items held in the game: weighty items and magical items. Further, we are advised that, in the future, food and clothing will also be available. This suggests a class hierarchy for the various types of game items. It should be capable of extending horizontally to include new categories of items, and vertically to further specialize the items. |
- | extern> http://www.cpjobling.org.uk/~eechris/at-m42/Case-Studies/ | + | |
+ | The initial class diagram is given in Figure 1. The '' | ||
+ | |||
+ | {{: | ||
+ | |||
+ | **Figure 1**: Initial class hierarchy | ||
+ | |||
+ | This leads us to develop the Groovy classes '' | ||
+ | |||
+ | <code groovy 1| Item class (at-m42/ | ||
+ | abstract class Item { | ||
+ | |||
+ | String toString() { // redefinition | ||
+ | def s = " | ||
+ | if ( description.size() > 0 ) { | ||
+ | s += " description: | ||
+ | } | ||
+ | return s | ||
+ | } | ||
+ | |||
+ | // ----- properties ----------------------------- | ||
+ | |||
+ | def name | ||
+ | def description = '' | ||
+ | def id | ||
+ | def value | ||
+ | |||
+ | } | ||
</ | </ | ||
- | ---- | + | <code groovy 1| WeightyItem class (at-m42/Case-Studies/ |
+ | extern> http:// | ||
+ | </ | ||
- | Notes ... | + | <code groovy 1| MagicalItem class (at-m42/ |
+ | extern> http://www.cpjobling.org.uk/ | ||
+ | </ | ||
- | Output: | + | When deploying a class hierarchy, we need to be assured that we a correctly initializing the objects and that any polymorphic behaviour operates as expected. This is the aim of this first iteration. All all three classes refine the '' |
- | Game: The Discworld | + | |
- | ===================== | + | |
- | Item: 1; name = Luggage: value = 1000; description: | + | |
- | | + | |
- | | + | |
- | ===== Iteration II: Augment | + | Following the discussions of [[at-m42:lecture8|Lecture 8]], we create |
- | <code groovy 1| Text-based user interface | + | <code groovy 1| WeightyItemTest class (at-m42/ |
- | extern> http:// | + | extern> http:// |
</ | </ | ||
- | ---- | + | The '' |
+ | |||
+ | Because we also intend unit testing other classes, we have a '' | ||
+ | |||
+ | <code groovy 1| GroovyTestSuite class (at-m42/Case-Studies/ | ||
+ | import groovy.util.GroovyTestSuite | ||
+ | import junit.framework.Test | ||
+ | import junit.textui.TestRunner | ||
+ | |||
+ | class AllTests { | ||
+ | |||
+ | static Test suite() { | ||
+ | def allTests = new GroovyTestSuite() | ||
+ | |||
+ | allTests.addTestSuite(WeightyItemTest.class) | ||
+ | allTests.addTestSuite(MagicalItemTest.class) | ||
+ | |||
+ | return allTests | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | TestRunner.run(AllTests.suite()) | ||
+ | </ | ||
- | Notes ... | + | We can easily add other '' |
- | Output: | + | An execution of the script results in the following test report: |
| | ||
+ | .. | ||
+ | Time: 0.005 | ||
| | ||
- | | + | |
- | ========================================== | + | |
- | Item: 1; name = Luggage: value = 1000; description: | + | |
- | Item: 2; name = Pointy hat: value = 10; | + | |
- | Item: 3; name = A bag of gold: value = 100; | + | |
- | + | ||
- | + | ||
- | Game: The Discworld : Player Details | + | |
- | ========================================== | + | |
- | Player: 1; Rincewind | + | |
- | Player: | + | |
- | + | ||
- | + | ||
- | Game: The Discworld : Available items | + | |
- | ========================================== | + | |
- | Item: 1; name = Luggage: value = 1000; description: | + | |
- | Item: 2; name = Pointy hat: value = 10; | + | |
- | Item: 3; name = A bag of gold: value = 100; | + | |
- | + | ||
- | + | ||
- | Game: The Discworld : Available items | + | |
- | ========================================== | + | |
- | Item: 3; name = A bag of gold: value = 100; | + | |
- | + | ||
- | + | ||
- | Game: The Discworld : Items being carried | + | |
- | ========================================== | + | |
- | Item: 1; name = Luggage: value = 1000; description: | + | |
- | Item: 2; name = Pointy hat: value = 10; | + | |
- | + | ||
- | + | ||
- | Game: The Discworld : Player Details | + | |
- | ========================================== | + | |
- | Player: 1; Rincewind (p1@diskworld.com) | + | |
- | Item: 1; name = Luggage: value = 1000; description: | + | |
- | Player: 2; Twoflower (p2@diskworld.com) | + | |
- | Item: 2; name = Pointy hat: value = 10; | + | |
- | + | ||
- | + | ||
- | Game: The Discworld : Available items | + | |
- | ========================================== | + | |
- | Item: 2; name = Pointy hat: value = 10; | + | |
- | Item: 3; name = A bag of gold: value = 100; | + | |
- | + | ||
- | + | ||
- | Game: The Discworld : Items being carried | + | |
- | ========================================== | + | |
- | Item: 1; name = Luggage: value = 1000; description: | + | |
- | + | ||
- | + | ||
- | Game: The Discworld : Player Details | + | |
- | ========================================== | + | |
- | Player: 1; Rincewind (p1@diskworld.com) | + | |
- | Item: 1; name = Luggage: value = 1000; description: | + | |
- | Player: 2; Twoflower (p2@diskworld.com) | + | |
- | ===== Iteration III: Reimplement the user interface ===== | ||
- | <code groovy 1| Reimplement the user interface | + | It confirms that we have the correct object initialization and polymorphic behaviour. Therefore, we have achieved the aim of this iteration. |
- | extern> http://www.cpjobling.org.uk/~eechris/at-m42/ | + | |
+ | ===== Iteration II: Demonstrate the Required Functionality ===== | ||
+ | |||
+ | having established that we can make use of the polymorphic effect, the aim of this iteration is to demonstrate that we can achieve the required functionality described in the use cases of the [[# | ||
+ | |||
+ | We have introduced the abstract class '' | ||
+ | |||
+ | {{:at-m42: | ||
+ | |||
+ | **Figure 2**: Class diagram | ||
+ | |||
+ | Clearly, we can base the implementation of these classes on those of [[at-m42: | ||
+ | <code groovy> | ||
+ | // class GameTest | ||
+ | // ... | ||
+ | | ||
+ | * Set up the fixture | ||
+ | */ | ||
+ | void setUp() { | ||
+ | game = new Game(name : ' | ||
+ | |||
+ | book = new WeightyItem(id : 1, name : ' | ||
+ | description : 'a maths text book', | ||
+ | weight : 1) | ||
+ | satchel = new WeightyItem (id : 2, name : ' | ||
+ | description : 'a carrier for school books and pencils', | ||
+ | weight : 5) | ||
+ | item3 = new WeightyItem (id : 2, name : 'a different satchel', | ||
+ | value : 10, weight : 5) | ||
+ | } | ||
</ | </ | ||
+ | (the '' | ||
- | ---- | + | The '' |
+ | <code groovy> | ||
+ | // Action class | ||
+ | void addWeightyItem() { | ||
+ | print(' | ||
+ | def itemId = Console.readInteger() | ||
+ | print(' | ||
+ | def name = Console.readLine() | ||
+ | print(' | ||
+ | def value = Console.readInteger() | ||
+ | print(' | ||
+ | def description = Console.readLine() | ||
+ | print(' | ||
+ | def weight = Console.readInteger() | ||
+ | |||
+ | def item = new WeightyItem(id : itemId, name : name, value : value, | ||
+ | description : description, | ||
+ | weight : weight) | ||
+ | |||
+ | game.addItem(item) | ||
+ | } | ||
+ | </ | ||
- | Notes... | + | We need a similar method '' |
+ | <code groovy> | ||
+ | // Action class | ||
+ | void addMagicalItem() { | ||
+ | print(' | ||
+ | def itemId = Console.readInteger() | ||
+ | print(' | ||
+ | def name = Console.readLine() | ||
+ | print(' | ||
+ | def value = Console.readInteger() | ||
+ | print(' | ||
+ | def description = Console.readLine() | ||
+ | print(' | ||
+ | def poetncy = Console.readInteger() | ||
+ | |||
+ | def item = new MagicalItem(id : itemId, name : name, value : value, | ||
+ | description : description, | ||
+ | potency : potency) | ||
+ | |||
+ | game.addItem(item) | ||
+ | } | ||
+ | </ | ||
- | Output: | + | Finally, we modify the Groovy script that presents a menu to a user and actions user choices. It is shown as Game 01. |
+ | |||
+ | <code groovy 1| Game 01: An adventure game with weighty and magical items (at-m42/ | ||
+ | extern> http:// | ||
+ | </ | ||
+ | |||
+ | To complete this iteration, we run our unit tests. Happily, they all pass. Next, we use the menu to carry out functional testing. an obvious strategy is to make choices (assisted by the various display options) that correspond to the use-cases identified earlier. An illustrative session (with the user data input shown in bold italic) is: | ||
< | < | ||
<pre> | <pre> | ||
- | e: | + | e: |
0: Quit | 0: Quit | ||
- | 1: Add new item | + | 1: Add new weighty |
- | 2: Display inventory | + | 2: Add new magical item |
- | 3: Display available items | + | 3: Display inventory |
- | 4: Display items being carried | + | 4: Display available items |
- | 5: Register new player | + | 5: Display items being carried |
- | 6: Display players | + | 6: Register new player |
- | 7: Pick up an item | + | 7: Display players |
- | 8: Drop an item | + | 8: Pick up an item |
+ | 9: Drop an item | ||
Enter choice>>>> | Enter choice>>>> | ||
- | Enter item id:< | + | |
- | Enter item name:< | + | Enter item id: < |
- | Enter item value:< | + | |
- | // ... | + | Enter item name: < |
- | // Present menu to the user | + | |
+ | Enter item value: < | ||
+ | |||
+ | Enter item description (return for none): < | ||
+ | |||
+ | Enter item weight: < | ||
+ | |||
+ | 0: Quit | ||
+ | 1: Add new weighty item | ||
+ | 2: Add new magical item | ||
+ | 3: Display inventory | ||
+ | 4: Display available items | ||
+ | 5: Display items being carried | ||
+ | 6: Register new player | ||
+ | 7: Display players | ||
+ | 8: Pick up an item | ||
+ | 9: Drop an item | ||
Enter choice>>>> | Enter choice>>>> | ||
- | Game: The Discworld | + | Enter item id: < |
- | ========================================== | + | |
- | Item: 1; name = Luggage: value = 1000; description: | + | |
- | ts of little legs; | + | |
- | // ... | + | Enter item name: < |
- | // Present menu to the user | + | |
- | | + | |
- | Enter player id:< | + | |
- | Enter player nickname:< | + | |
- | Enter player email address:< | + | |
- | // ... | + | |
- | // Present menu to the user | + | |
- | Enter choice>>>> | + | |
- | Game: The Discworld | + | Enter item value: < |
- | ========================================== | + | |
- | | + | Enter item description (return for none): < |
+ | |||
+ | Enter item potency: < | ||
+ | |||
+ | 0: Quit | ||
+ | 1: Add new weighty item | ||
+ | 2: Add new magical item | ||
+ | 3: Display inventory | ||
+ | 4: Display available items | ||
+ | 5: Display items being carried | ||
+ | 6: Register new player | ||
+ | 7: Display players | ||
+ | 8: Pick up an item | ||
+ | 9: Drop an item | ||
- | // ... | ||
- | // Present menu to the user | ||
- | Enter choice>>>> | ||
- | Enter item id:< | ||
- | Enter player id:< | ||
- | // ... | ||
- | // Present menu to the user | ||
Enter choice>>>> | Enter choice>>>> | ||
- | Game: The Discworld | + | |
+ | Game: The Discworld | ||
========================================== | ========================================== | ||
+ | WeightyItem: | ||
+ | MagicalItem: | ||
- | // ... | + | 0: Quit |
- | // Present menu to the user | + | 1: Add new weighty item |
- | Enter choice>>>> | + | 2: Add new magical item |
+ | 3: Display inventory | ||
+ | 4: Display available items | ||
+ | 5: Display items being carried | ||
+ | 6: Register new player | ||
+ | 7: Display players | ||
+ | 8: Pick up an item | ||
+ | 9: Drop an item | ||
- | Game: The Discworld : Items being carried | + | Enter choice>>>> |
- | ========================================== | + | |
- | Item: 1; name = Luggage: value = 1000; description: | + | |
- | ts of little legs; | + | |
- | // ... | ||
- | // Present menu to the user | ||
- | Enter choice>>>> | ||
- | Enter item id: < | + | Game closing ... thanks for playing |
+ | </pre> | ||
+ | </html> | ||
- | // ... | + | Having encountered no problems, we consider this iteration to be complete. |
- | // Present menu to the user | + | |
- | Enter choice>>>> | + | |
- | Game: The Discworld | + | ===== Iteration III: Provide User Feedback ===== |
- | ========================================== | + | |
- | Item: 1; name = Luggage: value = 1000; description: | + | Following a demonstration of the previous iteration, we have been asked to provide more feedback from the system and that commonly occurring errors can be handled. The following additional use-cases are to be implemented: |
- | ts of little legs; | + | * Remove an item |
+ | * Display a particular item | ||
+ | * Display selected items | ||
+ | * Display a particular player | ||
+ | * Display selected players | ||
+ | |||
+ | The aim of this iteration is to detect errors, give user feedback, and implement the additional use-cases. | ||
+ | |||
+ | We begin by addressing erroneous user data input. We have been advised that users may attempt to: | ||
+ | |||
+ | * Add a duplicate item | ||
+ | * Remove a non-existent item | ||
+ | * Register a duplicate player | ||
+ | * Remove a non-existent player | ||
+ | * Pick up an non-existent item | ||
+ | * Pick up an item that is being carried by another player | ||
+ | * A non-existent player attempts to pick up an item | ||
+ | * A non-existent item is dropped | ||
+ | * An item that was not being carried is dropped | ||
+ | * Display a non-existent item | ||
+ | * Display a non-existent player | ||
+ | |||
+ | Clearly, we must check these scenarios, take some appropriate action, and then inform the user. We descide that most of the checks be the responsibility of the '' | ||
+ | |||
+ | We also decide that it is the '' | ||
+ | |||
+ | <code groovy 1|Extended Game class (at-m42/ | ||
+ | extern> http:// | ||
+ | </ | ||
+ | |||
+ | Because we have changed the signature of the method '' | ||
+ | |||
+ | <code groovy> | ||
+ | /** | ||
+ | * Test that the Game had one Item after removal of | ||
+ | * an Item known to be in the Game | ||
+ | */ | ||
+ | void testRemoveItem_1() { | ||
+ | // | ||
+ | // book is created in the fixture | ||
+ | | ||
+ | def pre = game.inventory.size() | ||
+ | | ||
+ | def post = game.inventory.size() | ||
+ | |||
+ | | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Test that the correct message is available to a client | ||
+ | */ | ||
+ | void testRemoveItem_2() { | ||
+ | // | ||
+ | // book is created in the fixture | ||
+ | | ||
+ | def actual = game.removeItem(book.id) | ||
+ | def expected = 'Item removed' | ||
+ | |||
+ | | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Test that the correct message is available to a client | ||
+ | */ | ||
+ | void testRemoveItem_3() { | ||
+ | def actual = game.removeItem(book.id) | ||
+ | def expected = ' | ||
+ | |||
+ | | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Notice that we make use of //safe navigation// | ||
+ | <code groovy> | ||
+ | String removeItem(Integer itemId) { | ||
+ | def message | ||
+ | if ( inventory.containsKey(itemId) == true ) { | ||
+ | ... | ||
+ | item.carrier? | ||
+ | ... | ||
+ | } | ||
+ | else { | ||
+ | ... | ||
+ | } | ||
+ | return message | ||
+ | } | ||
+ | </ | ||
+ | This means that we don't have to make an explicit check that the '' | ||
+ | |||
+ | A large number of other unit tests are are needed to test all the possible paths through the new '' | ||
+ | |||
+ | We also decide that the '' | ||
+ | |||
+ | To implement the remaining new use-cases, we introduce two more flexible display methods. Both make use of regular expressions with '' | ||
+ | <code groovy> | ||
+ | import console.Console | ||
+ | |||
+ | class Action { | ||
+ | |||
+ | // ... | ||
+ | |||
+ | def removePublication() { | ||
+ | print(' | ||
+ | def itemId = Console.readInteger() | ||
+ | |||
+ | def message = game.removeItem(itemId) | ||
+ | println " | ||
+ | } | ||
+ | |||
+ | // ... | ||
+ | |||
+ | def displayOneItem() { | ||
+ | print(' | ||
+ | def itemId = Console.readInteger() | ||
+ | |||
+ | def item = game.inventory[itemId] | ||
+ | if ( item != null ) { | ||
+ | this.printHeader(' | ||
+ | println item | ||
+ | } | ||
+ | else { | ||
+ | println ' | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // ... | ||
+ | |||
+ | def displaySelectedItems() { | ||
+ | print(' | ||
+ | def pattern = Console.readLine() | ||
+ | pattern = ' | ||
+ | def found = false | ||
+ | |||
+ | this.printHeader(' | ||
+ | game.inventory.each { itemId, item -> | ||
+ | if ( itemId.toString() =~ pattern ) { | ||
+ | found = true | ||
+ | println " ${item}" | ||
+ | } | ||
+ | } | ||
+ | |||
+ | if (found == false) { | ||
+ | println ' | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // ... | ||
+ | |||
+ | def displayOnePlayer() { | ||
+ | print(' | ||
+ | def playerId = Console.readInteger() | ||
+ | |||
+ | def player = game.players[playerId] | ||
+ | if ( player != null ) { | ||
+ | this.printHeader(' | ||
+ | println player | ||
+ | def items = player.inventory | ||
+ | items.each { itemId, item -> println " ${item}" | ||
+ | } | ||
+ | else { | ||
+ | println ' | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // ... | ||
+ | |||
+ | def displaySelectedPlayers() { | ||
+ | print(' | ||
+ | def pattern | ||
+ | pattern | ||
+ | def found = false | ||
+ | |||
+ | this.printHeader(' | ||
+ | game.players.each { playerId, player -> | ||
+ | if ( playerId.toString() | ||
+ | found | ||
+ | println player | ||
+ | def items = player.inventory | ||
+ | items.each { itemId, item -> println " ${item}" | ||
+ | } | ||
+ | } | ||
+ | |||
+ | if (found | ||
+ | println ' | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // ... | ||
+ | |||
+ | private printHeader(detail) { | ||
+ | println " | ||
+ | println '===============================\n' | ||
+ | } | ||
+ | |||
+ | // ----- properties ----------------------- | ||
+ | |||
+ | private game | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | Note the introduction of the private '' | ||
+ | |||
+ | All that remains is to modify the previous Groovy script to present and action a slightly different menu to the user. a partial listing is shown in Game 02. | ||
+ | |||
+ | <code groovy|Game 02: An adventure game with weighty and magical items with error detection and user feedback (at-m42/ | ||
+ | import console.Console | ||
+ | |||
+ | def readMenuSelection() { | ||
+ | // ... | ||
+ | println(' | ||
+ | |||
+ | // ... | ||
+ | println(' | ||
+ | println(' | ||
+ | |||
+ | // ... | ||
+ | println(' | ||
+ | println(' | ||
+ | |||
+ | // ... | ||
+ | print(' | ||
+ | return Console.readInteger() | ||
+ | } | ||
+ | |||
+ | // make the Action object | ||
+ | def action | ||
+ | |||
+ | // make first selection | ||
+ | def choice | ||
+ | while (choice != 0) { | ||
+ | switch (choice) { | ||
+ | case 1: | ||
+ | // ... | ||
+ | case 3: | ||
+ | action.removeItem() | ||
+ | break | ||
+ | // ... | ||
+ | case 5: | ||
+ | action.displaySelectedItems() | ||
+ | break | ||
+ | case 6: | ||
+ | action.displayOneItem() | ||
+ | break | ||
+ | // ... | ||
+ | case 11: | ||
+ | action.displayeSelectedPlayers() | ||
+ | break | ||
+ | case 12: | ||
+ | action.displayOnePlayer() | ||
+ | break | ||
+ | // ... | ||
+ | default: | ||
+ | println " | ||
+ | } | ||
+ | choice = readMenuSelection() | ||
+ | } | ||
+ | println ' | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | < | ||
+ | 0: Quit | ||
+ | |||
+ | 1: Add new weighty item | ||
+ | 2: Add new magical item | ||
+ | 3: Remove an item | ||
+ | |||
+ | 4: Display inventory | ||
+ | 5: Display selected items | ||
+ | 6: Dsiplay one item | ||
+ | 7: Display available items | ||
+ | 8: Display items being carried | ||
+ | |||
+ | 9: Register new player | ||
+ | 10: Display all players | ||
+ | 11: Display selected players | ||
+ | 12: Display one player | ||
+ | |||
+ | 13: Pick up an item | ||
+ | 14: Drop an item | ||
+ | |||
+ | Enter choice>>>> | ||
+ | |||
+ | Enter item id: < | ||
+ | |||
+ | Enter item name: < | ||
+ | |||
+ | Enter item value: < | ||
+ | |||
+ | Enter item description | ||
+ | |||
+ | Enter item potency: < | ||
+ | |||
+ | Result: Item added | ||
+ | |||
+ | 0: Quit | ||
+ | |||
+ | 1: Add new weighty item | ||
+ | 2: Add new magical item | ||
+ | 3: Remove an item | ||
+ | |||
+ | 4: Display inventory | ||
+ | 5: Display selected items | ||
+ | 6: Dsiplay one item | ||
+ | 7: Display available items | ||
+ | 8: Display items being carried | ||
+ | |||
+ | 9: Register new player | ||
+ | 10: Display all players | ||
+ | 11: Display selected players | ||
+ | 12: Display one player | ||
+ | |||
+ | 13: Pick up an item | ||
+ | 14: Drop an item | ||
- | // ... | ||
- | // Present menu to the user | ||
Enter choice>>>> | Enter choice>>>> | ||
- | Game: The Discworld : Items being carried | + | Game: The Discworld: |
- | ========================================== | + | =============================== |
+ | |||
+ | WeightyItem: | ||
+ | WeightyItem: | ||
+ | MagicalItem: | ||
+ | |||
+ | 0: Quit | ||
+ | |||
+ | 1: Add new weighty item | ||
+ | 2: Add new magical item | ||
+ | 3: Remove an item | ||
+ | |||
+ | 4: Display inventory | ||
+ | 5: Display selected items | ||
+ | 6: Dsiplay one item | ||
+ | 7: Display available items | ||
+ | 8: Display items being carried | ||
+ | |||
+ | 9: Register new player | ||
+ | 10: Display all players | ||
+ | 11: Display selected players | ||
+ | 12: Display one player | ||
+ | |||
+ | 13: Pick up an item | ||
+ | 14: Drop an item | ||
+ | |||
+ | Enter choice>>>> | ||
+ | |||
+ | Enter start of item ids: < | ||
+ | |||
+ | Game: The Discworld: Selected publications display | ||
+ | =============================== | ||
+ | |||
+ | | ||
+ | | ||
+ | |||
+ | 0: Quit | ||
+ | |||
+ | 1: Add new weighty item | ||
+ | 2: Add new magical item | ||
+ | 3: Remove an item | ||
+ | |||
+ | 4: Display inventory | ||
+ | 5: Display selected items | ||
+ | 6: Dsiplay one item | ||
+ | 7: Display available items | ||
+ | 8: Display items being carried | ||
+ | |||
+ | 9: Register new player | ||
+ | 10: Display all players | ||
+ | 11: Display selected players | ||
+ | 12: Display one player | ||
+ | |||
+ | 13: Pick up an item | ||
+ | 14: Drop an item | ||
- | // ... | ||
- | // Present menu to the user | ||
Enter choice>>>> | Enter choice>>>> | ||
- | Game closing ... thanks for playing | ||
- | e: | + | Game closing ... thanks for playing |
</ | </ | ||
</ | </ | ||
+ | |||
+ | At this point we consider the iteration complete. | ||
+ | |||
+ | ===== Iteration IV: Enforce Constraints ===== | ||
+ | |||
+ | With graphical notations such as the UML, it is often difficult to record the finer details of a system' | ||
+ | |||
+ | We can make assertions about our models by adding textual annotations to model elements. For example Figure 3 is a class diagram that illustrates the constraints placed on the Player class such that no player can carry more that a certain number of items. | ||
+ | |||
+ | {{: | ||
+ | |||
+ | **Figure 3**: A constraint shown as a textual annotation. | ||
+ | |||
+ | The text in the note describes the constraint. It may be informal English, as is the case here, or it may be stated more formally. In any event, we must ensure that the implementation that this constraint is not violated. To accomplish this, we have updated the '' | ||
+ | <code groovy> | ||
+ | class Player { | ||
+ | | ||
+ | // ... | ||
+ | | ||
+ | // ----- properties ----------------------------- | ||
+ | |||
+ | def nickname | ||
+ | def email | ||
+ | def id | ||
+ | static public final LIMIT = 4 | ||
+ | def inventory = [ : ] | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | We can then make checks in the '' | ||
+ | |||
+ | <code groovy> | ||
+ | // class: Game | ||
+ | String pickupItem(Integer itemId, Integer playerId) { | ||
+ | def message | ||
+ | if ( inventory.containsKey(itemId) == true ) { | ||
+ | def item = inventory[itemId] | ||
+ | if ( item.carrier == null ) { | ||
+ | if ( players.containsKey(playerId) == true ) { | ||
+ | def player = players[playerId] | ||
+ | if (player.inventory.size() < Player.LIMIT) { | ||
+ | player.pickUp(item) | ||
+ | message = 'Item picked up' | ||
+ | } | ||
+ | else { | ||
+ | message = ' | ||
+ | } | ||
+ | } | ||
+ | else { | ||
+ | message = ' | ||
+ | } | ||
+ | } | ||
+ | else { | ||
+ | message = ' | ||
+ | } | ||
+ | } | ||
+ | else { | ||
+ | message = ' | ||
+ | } | ||
+ | return message | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | As usual, we update the '' | ||
+ | <code groovy> | ||
+ | // class: GameTest | ||
+ | /** | ||
+ | * Test that over limit message works | ||
+ | */ | ||
+ | void testPickupItemCannotExceedItemLimit() { | ||
+ | def item4 = new WeightyItem(id : 444, name : 'item 4', value : 4, weight : 4) | ||
+ | def item5 = new MagicalItem(id : 555, name : 'item 5', value : 5, potency : 5) | ||
+ | def item6 = new WeightyItem(id : 666, name : 'item 6', value : 6, weight : 6) | ||
+ | // | ||
+ | // book, satchel and item3 are created in the fixture | ||
+ | def itemList = [book, satchel, item3, item4, item5, item6] | ||
+ | |||
+ | game.registerPlayer(player) | ||
+ | |||
+ | def actual | ||
+ | itemList.each{ item -> | ||
+ | game.addItem(item) | ||
+ | actual = game.pickupItem(item.id, | ||
+ | } | ||
+ | |||
+ | def expected = ' | ||
+ | |||
+ | assertTrue(' | ||
+ | } | ||
+ | </ | ||
+ | Since Groovy' | ||
+ | |||
+ | This is an example of a //loop variant//. although it does not concern us here, loop invariants are widely used in formal approaches to software development where proof of correctness is important. For our purposes, we just need to demonstrate that if we start at some object and follow a sequence of object links, then we arrive back at the same object. The object diagram of Figure 4 illustrates this. | ||
+ | |||
+ | {{: | ||
+ | |||
+ | **Figure 4**: An Item-Player loop invariant. | ||
+ | |||
+ | The figure shows that if we start from a given '' | ||
+ | <code groovy> | ||
+ | // class: Game | ||
+ | private void checkItemCarrierLoopInvariant(String methodName) { | ||
+ | def items = inventory.values().asList() | ||
+ | |||
+ | def carriedItems = items.findAll{ item -> item.carrier != null } | ||
+ | |||
+ | def allOK = carriedItems.every { item -> | ||
+ | item.carrier.inventory.containsKey(item.id) | ||
+ | } | ||
+ | |||
+ | if (! allOK ) { | ||
+ | throw new Exception(" | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Since the violation of an invariant indicates that a serious error has occurred, we terminate the system by throwing an '' | ||
+ | |||
+ | As before, we only check methods that are likely to cause a violation. In this case it is just the method '' | ||
+ | |||
+ | <code groovy> | ||
+ | // class: Game | ||
+ | // ... | ||
+ | if (player.inventory.size() < Player.LIMIT) { | ||
+ | player.pickUp(item) | ||
+ | this.checkItemCarrierLoopInvariant(' | ||
+ | message = 'Item picked up' | ||
+ | } | ||
+ | else { | ||
+ | message = ' | ||
+ | } | ||
+ | // ... | ||
+ | </ | ||
+ | |||
+ | Before we finish, we must create at least one unit test to check that the expected '' | ||
+ | |||
+ | One solution is to create a '' | ||
+ | <code groovy> | ||
+ | class MockPlayer extends Player { | ||
+ | | ||
+ | Boolean pickUp(Item item) { | ||
+ | if (! inventory.containsKey(item.id)) { | ||
+ | // | ||
+ | // Normal behviour commented out | ||
+ | // inventory[item.id] = item | ||
+ | item.pickedUpBy(this) | ||
+ | return true | ||
+ | } | ||
+ | else { | ||
+ | return false | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | We create a '' | ||
+ | |||
+ | <code groovy> | ||
+ | // class: GameTest | ||
+ | void testCheckItemCarrierLoopInvariant() { | ||
+ | def mockPlayer = new MockPlayer(id : 1234, nickname : ' | ||
+ | email : ' | ||
+ | game.registerPlayer(mockPlayer) | ||
+ | game.addItem(book) | ||
+ | game.addItem(satchel) | ||
+ | |||
+ | try { | ||
+ | game.pickupItem(book.id, | ||
+ | fail(' | ||
+ | } catch (Exception e) { | ||
+ | // ignore exception | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Note that the method '' | ||
+ | |||
+ | Happily all the tests in the '' | ||
===== Exercises ===== | ===== Exercises ===== |
at-m42/casestudies/cs04.txt · Last modified: 2011/01/14 12:59 by 127.0.0.1