at-m42:casestudies:cs04
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
at-m42:casestudies:cs04 [2009/04/15 08:14] – eechris | at-m42:casestudies:cs04 [2011/01/14 12:59] (current) – external edit 127.0.0.1 | ||
---|---|---|---|
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 510: | Line 510: | ||
</ | </ | ||
- | Note the introduction of the private '' | + | 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. | 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. | ||
Line 693: | Line 693: | ||
At this point we consider the iteration complete. | At this point we consider the iteration complete. | ||
- | ===== Exercises | + | ===== 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 '' | ||
+ | |||
at-m42/casestudies/cs04.txt · Last modified: 2011/01/14 12:59 by 127.0.0.1