楽しげなお題だったのでチャレンジしてみました。
ざっと思いつく実装を書いただけでろくにクタってもないため、ツッコミどころ満載かもですがどうぞお手柔らかに。^^; Squeak4.2J でファイルインして動かせます。
少し読みやすく圧縮整形したもの。
TestCase subclass: #'商品Test' 商品Test>>test01hash | 商品1 商品2 商品3 商品4 商品5 | 商品1 := 商品 ID: 1 製品名: #test 価格: 100. 商品2 := 商品 ID: 1 製品名: #test 価格: 100. 商品3 := 商品 ID: 2 製品名: #test 価格: 100. 商品4 := 商品 ID: 1 製品名: #test2 価格: 100. 商品5 := 商品 ID: 1 製品名: #test 価格: 101. self assert: 商品1 hash = 商品2 hash. self deny: 商品1 hash = 商品3 hash. self deny: 商品1 hash = 商品4 hash. self deny: 商品1 hash = 商品5 hash. 商品Test>>test02等価 | 商品1 商品2 商品3 商品4 商品5 | 商品1 := 商品 ID: 1 製品名: #test 価格: 100. 商品2 := 商品 ID: 1 製品名: #test 価格: 100. 商品3 := 商品 ID: 2 製品名: #test 価格: 100. 商品4 := 商品 ID: 1 製品名: #test2 価格: 100. 商品5 := 商品 ID: 1 製品名: #test 価格: 101. self assert: 商品1 = 商品2. self deny: 商品1 = 商品3. self deny: 商品1 = 商品4. self deny: 商品1 = 商品5. 商品Test>>test03辞書のキーになることができる | 商品1 辞書 | 商品1 := 商品 ID: 1 製品名: #test 価格: 100. 辞書 := Dictionary new. 辞書 at: 商品1 put: 1. self assert: (辞書 at: 商品1 ifAbsent: [0]) = 1
TestCase subclass: #'自動販売機Test' 自動販売機Test>>test01自動販売機を生成できる | 販売機 | 販売機 := 自動販売機 new. self assert: 販売機 class == 自動販売機 自動販売機Test>>test02千円札を挿入することができる | 販売機 | 販売機 := 自動販売機 new. self shouldnt: [販売機 投入: 1000] raise: Error 自動販売機Test>>test03x1円玉と5円玉以外の硬貨を投入できる | 販売機 | 販売機 := 自動販売機 new. self shouldnt: [販売機 投入: 500] raise: Error. self shouldnt: [販売機 投入: 100] raise: Error. self shouldnt: [販売機 投入: 50] raise: Error. self shouldnt: [販売機 投入: 10] raise: Error 自動販売機Test>>test04x1円玉と5円玉は使うことができずエラーになる | 販売機 | 販売機 := 自動販売機 new. self should: [販売機 投入: 5] raise: Error. self should: [販売機 投入: 1] raise: Error 自動販売機Test>>test05現在投入されている合計金額を算出できる | 販売機 | 販売機 := 自動販売機 new. 販売機 投入: 1000. 販売機 投入: 500. self assert: 販売機 投入金額 = 1500 自動販売機Test>>test06ID1コーラ5本120円を在庫として保持できる | 販売機 コーラ | 販売機 := 自動販売機 new. コーラ := 商品 ID: 1 製品名: #コーラ 価格: 120. self assert: (販売機 在庫 at:コーラ) = 5 自動販売機Test>>test07お金を投入すると現在購入できるIDを算出する | 販売機 コーラ 水 | 販売機 := 自動販売機 new. コーラ := 商品 ID: 1 製品名: #コーラ 価格: 120. 水 := 商品 ID: 3 製品名: #水 価格: 100. 販売機 投入: 50. self assert: 販売機 購入可能商品群 isEmpty. 販売機 投入: 50. self assert: 販売機 購入可能商品群 asArray = {水 ID}. 販売機 投入: 50. self assert: 販売機 購入可能商品群 asSet = {水 ID. コーラ ID} asSet. 自動販売機Test>>test08購入できるIDを指定してジュースを買うとコーラの在庫が減る | 販売機 コーラ | 販売機 := 自動販売機 new. コーラ := 商品 ID: 1 製品名: #コーラ 価格: 120. 販売機 投入: 100. 販売機 投入: 50. 販売機 購入: コーラ ID. self assert: (販売機 在庫 at: コーラ) = 4 自動販売機Test>>test09現在の売上金額が算出される | 販売機 コーラ | 販売機 := 自動販売機 new. コーラ := 商品 ID: 1 製品名: #コーラ 価格: 120. 3 timesRepeat: [販売機 投入: 100]. 6 timesRepeat: [販売機 投入: 10]. 販売機 購入: コーラ ID. self assert: (販売機 売上金額 = 120) 自動販売機Test>>test10現在の在庫数が算出される | 販売機 コーラ | 販売機 := 自動販売機 new. コーラ := 商品 ID: 1 製品名: #コーラ 価格: 120. 3 timesRepeat: [販売機 投入: 100]. 6 timesRepeat: [販売機 投入: 10]. 販売機 購入: コーラ ID. self assert: (販売機 在庫 at: コーラ) = 4 自動販売機Test>>test11在庫切れを考慮する | 販売機 コーラ | 販売機 := 自動販売機 new. コーラ := 商品 ID: 1 製品名: #コーラ 価格: 120. 5 timesRepeat: [販売機 投入: 100. 2 timesRepeat: [販売機 投入: 10]. 販売機 購入: コーラ ID]. self assert: 販売機 在庫切れ商品群 = {コーラ ID} 自動販売機Test>>test12千円札と500円と100円と50円と10円硬貨を保持できる | 販売機 | 販売機 := 自動販売機 new. self assert: 販売機 保持紙幣と硬貨 keys asSet = #(1000 500 100 50 10) asSet 自動販売機Test>>test13千円札5枚と硬貨はそれぞれ10枚保持する | 販売機 | 販売機 := 自動販売機 new. self assert: (販売機 保持紙幣と硬貨 at: 1000) = 5. #(500 100 50 10) do: [:key | self assert: (販売機 保持紙幣と硬貨 at: key) = 10] 自動販売機Test>>test14x500円玉を投入すると保持硬貨数が増える | 販売機 投入前 差分 | 販売機 := 自動販売機 new. 投入前 := 販売機 保持紙幣と硬貨 copy. 販売機 投入: 500. 差分 := 販売機 保持紙幣と硬貨 keys collect: [:key | key -> ((販売機 保持紙幣と硬貨 at: key) - (投入前 at: key))] thenSelect: [:assoc | assoc value ~= 0]. self assert: 差分 = {500->1} 自動販売機Test>>test15ジュースが1つ購入されるとお釣りとしてお金が減算される | 販売機 投入前 差分 コーラ | 販売機 := 自動販売機 new. コーラ := 商品 ID: 1 製品名: #コーラ 価格: 120. 投入前 := 販売機 保持紙幣と硬貨 copy. 販売機 投入: 500. 販売機 購入: コーラ ID. 差分 := 販売機 保持紙幣と硬貨 keys asSet collect: [:key | key -> ((販売機 保持紙幣と硬貨 at: key) - (投入前 at: key))] thenSelect: [:assoc | assoc value ~= 0]. self assert: 差分 = {500->1. 100-> -3. 50-> -1. 10-> -3} asSet 自動販売機Test>>test16お釣りが足りるか判断できる | 販売機 | 販売機 := 自動販売機 new. 販売機 投入: 1000. self assert: (販売機 保持紙幣と硬貨 at: 100) = 10. self assert: (販売機 保持紙幣と硬貨 at: 50) = 10. self assert: (販売機 保持紙幣と硬貨 at: 10) = 10. self assert: (販売機 購入後残金返却可か: 100). 販売機 保持紙幣と硬貨 at: 100 put: 0. self assert: (販売機 購入後残金返却可か: 100). 販売機 保持紙幣と硬貨 at: 50 put: 5. self deny: (販売機 購入後残金返却可か: 100) 自動販売機Test>>test17現在のお釣り用のお金が算出される | 販売機 | 販売機 := 自動販売機 new. self assert: (販売機 保持紙幣と硬貨 at: 500) = 10. self assert: (販売機 保持紙幣と硬貨 at: 100) = 10. self assert: (販売機 保持紙幣と硬貨 at: 50) = 10. self assert: (販売機 保持紙幣と硬貨 at: 10) = 10 自動販売機Test>>test18xID2レッドブル200円5本を追加 | 販売機 レッドブル | 販売機 := 自動販売機 new. レッドブル := 商品 ID: 2 製品名: #レッドブル 価格: 200. self assert: (販売機 在庫 at: レッドブル) = 5 自動販売機Test>>test19xID3水100円5本を追加 | 販売機 水 | 販売機 := 自動販売機 new. 水 := 商品 ID: 3 製品名: #水 価格: 100. self assert: (販売機 在庫 at: 水) = 5 自動販売機Test>>test20お釣りが足りないと購入できない | 販売機 コーラ | 販売機 := 自動販売機 new. 販売機 投入: 1000. self assert: (販売機 保持紙幣と硬貨 at: 100) = 10. self assert: (販売機 保持紙幣と硬貨 at: 50) = 10. self assert: (販売機 保持紙幣と硬貨 at: 10) = 10. 販売機 保持紙幣と硬貨 at: 100 put: 0. 販売機 保持紙幣と硬貨 at: 50 put: 5. コーラ := 商品 ID: 1 製品名: #コーラ 価格: 120. self deny: (販売機 購入可能商品群 includes: コーラ ID)
Object subclass: #'商品' instanceVariableNames: 'ID 製品名 価格' 商品>>ID ^ ID 商品>>ID: anInteger ID := anInteger 商品>>価格 ^ 価格 商品>>価格: anInteger 価格 := anInteger 商品>>製品名 ^ 製品名 商品>>製品名: aSymbol 製品名 := aSymbol 商品>>printOn: stream stream nextPutAll: 製品名 printString 商品>>= a商品 a商品 class == self class ifFalse: [^false]. a商品 ID = ID ifFalse: [^false]. a商品 製品名 = 製品名 ifFalse: [^false]. a商品 価格 = 価格 ifFalse: [^false]. ^true 商品>>hash ^(ID hash bitXor: 製品名 hash) bitXor: 価格 hash 商品 class>>ID: ID 製品名: 製品名 価格: 価格 ^self new ID: ID; 製品名: 製品名; 価格: 価格; yourself
Object subclass: #'自動販売機' instanceVariableNames: '投入金額 在庫 売上金額 保持紙幣と硬貨' 自動販売機>>投入: 紙幣か硬貨 (self 使用可能紙幣と硬貨 includes: 紙幣か硬貨) ifFalse: [self error: 'この紙幣または硬貨は使えません']. 投入金額 := 投入金額 + 紙幣か硬貨. 保持紙幣と硬貨 at: 紙幣か硬貨 put: (保持紙幣と硬貨 at: 紙幣か硬貨) + 1 自動販売機>>使用可能紙幣と硬貨 ^保持紙幣と硬貨 keys asSet 自動販売機>>保持紙幣と硬貨 ^保持紙幣と硬貨 自動販売機>>在庫 ^在庫 自動販売機>>在庫切れ商品群 ^在庫 keys select: [:key | (在庫 at: key) = 0] thenCollect: [:key | key ID] 自動販売機>>売上金額 ^売上金額 自動販売機>>投入金額 ^投入金額 自動販売機>>購入可能商品群 ^在庫 keys select: [:key | (在庫 at: key) > 0 and: [key 価格 <= 投入金額] and: [self 購入後残金返却可か: key 価格]] thenCollect: [:key | key ID] 自動販売機>>initialize super initialize. 投入金額 := 売上金額 := 0. 在庫 := Dictionary new. 保持紙幣と硬貨 := {1000->5. 500->10. 100->10. 50->10. 10->10} as: Dictionary. 5 timesRepeat: [self 補充: (商品 ID: 1 製品名: #コーラ 価格: 120)]. 5 timesRepeat: [self 補充: (商品 ID: 2 製品名: #レッドブル 価格: 200)]. 5 timesRepeat: [self 補充: (商品 ID: 3 製品名: #水 価格: 100)] 自動販売機>>購入: 商品ID | 購入可能商品群 購入商品 | 購入可能商品群 := self 購入可能商品群. (購入可能商品群 includes: 商品ID) ifFalse: [self error: '購入できません']. 購入商品 := 在庫 keys detect: [:each | each ID = 商品ID]. 在庫 at: 購入商品 put: (在庫 at: 購入商品) - 1. 売上金額 := 売上金額 + 購入商品 価格. 投入金額 := 投入金額 - 購入商品 価格. self 差額返却 自動販売機>>補充: a商品 ^在庫 at: a商品 put: (在庫 at: a商品 ifAbsentPut: [0]) + 1 自動販売機>>差額返却 保持紙幣と硬貨 keys sort reverseDo: [:key | [key <= 投入金額] whileTrue: [ 投入金額 := 投入金額 - key. 保持紙幣と硬貨 at: key put: (保持紙幣と硬貨 at: key) - 1]] 自動販売機>>購入後残金返却可か: 購入金額 | 返却額 現状 | 返却額 := 投入金額 - 購入金額. 現状 := 保持紙幣と硬貨 copy. 現状 keys sort reverseDo: [:key | [key <= 返却額 and: [(現状 at: key) > 0]] whileTrue: [ 返却額 := 返却額 - key. 現状 at: key put: (現状 at: key) - 1]]. ^返却額 = 0