今回は自動販売機ロジックについて学習したのでアウトプットします。
今回はその2回目です。
ちなみに私はMac触り始めて半年の者です。(ガチガチの初心者です)
初学者のアウトプット記事ですので、間違っている点など多々あるかと思いますが、暖かい目で見守っていただけますと幸いです。
#はじめに
現在、私はMENTAで、ヤマタクメンターにご指導いただいております。
ヤマタクメンターのURL https://menta.work/user/1840
今回は課題として、自動販売機のロジックをXcodeのPlaygroundでコーディングしてみました。
簡単に言うと自動販売機の仕組みをコードで表現しましたみたいなことですね。
第一回目のコード Swift初学者が自動販売機ロジックを書いてみたら…(その1)
一発目から一回リファクタリングしてみたんですが、どうも納得いかなくてサンプルコードを参考にして、結局2回くらい書き直しました。
#仕様
・お金を入力する
・お金は1、5、10、50、100、500、1000円から何枚入力するか指定する
・1、5円は使用不可、その他のお金のみ使用可能とする
・水、コーヒー、エナジードリンクの3種類から指定する
・飲み物が買われたら、その飲み物の在庫数を-1して、お釣りを出力
・お金が足りなければ、お金が足りません!と出力
・在庫がなければ、すみません!在庫がありません!と出力
#実装コード
import UIKit
//ドリンクの種類と金額
enum DrinkType {
case coffee
case water
case energyDrink
var price: Int {
switch self {
case .coffee: return 150
case .water: return 100
case .energyDrink: return 200
}
}
}
struct DrinkModel {
var type: DrinkType
var stock: Int
}
struct calculateMoney {
var inputedYen: Int = 0
var change:Int = 0
enum Money: Int {
case oneYen = 1
case fiveYen = 5
case tenYen = 10
case fiftyYen = 50
case onehundredYen = 100
case fivehundredYen = 500
case onethousandYen = 1000
}
func MoneyCount(moneyType: Money,count: Int) -> Int {
switch moneyType {
case .oneYen:
return Money.oneYen.rawValue * count
case .fiveYen:
return Money.fiveYen.rawValue * count
case .tenYen:
return Money.tenYen.rawValue * count
case .fiftyYen:
return Money.fiftyYen.rawValue * count
case .onehundredYen:
return Money.onehundredYen.rawValue * count
case .fivehundredYen:
return Money.fivehundredYen.rawValue * count
case .onethousandYen:
return Money.onethousandYen.rawValue * count
}
}
//使用不可のお金が入力されているか判別
func selectDisabledMoney() -> Bool {
guard oneYenCalculate > 0 || fiveYenCalculate > 0 else {
return false
}
return true
}
//使用不可のお金は出力して、使用可能なお金はinputedYenとして代入する
mutating func checkInputedYen() {
if selectDisabledMoney() {
inputedYen = tenYenCalculate + fiftyYenCalculate + onehundredYenCalculate + fivehundredYenCalculate + onethousandYenCalculate
print("1円と5円は使用できません。\(oneYenCalculate + fiveYenCalculate)円お返しします。")
}else{
inputedYen = tenYenCalculate + fiftyYenCalculate + onehundredYenCalculate + fivehundredYenCalculate + onethousandYenCalculate
}
}
mutating func calculateChange(money: Int,price: Int) {
change = money - price
}
}
var calculate = calculateMoney()
//何円を何枚使用するか入力
let oneYenCalculate = calculate.MoneyCount(moneyType: .oneYen, count: 0)
let fiveYenCalculate = calculate.MoneyCount(moneyType: .fiveYen, count: 0)
let tenYenCalculate = calculate.MoneyCount(moneyType: .tenYen, count: 0)
let fiftyYenCalculate = calculate.MoneyCount(moneyType: .fiftyYen, count: 0)
let onehundredYenCalculate = calculate.MoneyCount(moneyType: .onehundredYen, count: 0)
let fivehundredYenCalculate = calculate.MoneyCount(moneyType: .fivehundredYen, count: 0)
let onethousandYenCalculate = calculate.MoneyCount(moneyType: .onethousandYen, count: 1)
//ドリンクが購入できることを確証する
protocol BuyDrinkValidatable {}
extension BuyDrinkValidatable {
func validateDrinkBuyable(with drink: DrinkModel,inputYen: Int,change: Int) -> Bool {
if drink.stock > .zero && drink.type.price <= inputYen && change >= 0 {
return true
}else{
return false
}
}
}
class VendingMachine: BuyDrinkValidatable {
// ドリンクの初期在庫数
var coffee = DrinkModel(type: .coffee, stock: 5)
var water = DrinkModel(type: .water, stock: 5)
var energyDrink = DrinkModel(type: .energyDrink, stock: 5)
// ドリンクを購入する関数
func buyDrink(type: DrinkType, inputedYen: Int) -> Bool {
calculate.checkInputedYen()
switch type {
case .coffee:
let isBuyable = validateDrinkBuyable(with: coffee, inputYen: calculate.inputedYen, change: calculate.change)
if isBuyable {
reduceStock(type: type)
calculate.calculateChange(money: calculate.inputedYen, price: DrinkType.coffee.price)
print("\(type)をどうぞ!\(calculate.change)円のお返しです!")
}else if coffee.stock == .zero{
print("すみません在庫切れです!\(calculate.inputedYen)円お返しします")
}else{
print("お金が足りません!\(calculate.inputedYen)円お返しします")
}
return isBuyable
case .water:
let isBuyable = validateDrinkBuyable(with: water, inputYen: inputedYen, change: calculate.change)
if isBuyable {
reduceStock(type: type)
calculate.calculateChange(money: calculate.inputedYen, price: DrinkType.water.price)
print("\(type)をどうぞ!\(calculate.change)円のお返しです!")
}else if water.stock == .zero{
print("すみません在庫切れです!\(calculate.inputedYen)円お返しします")
}else{
print("お金が足りません!\(calculate.inputedYen)円お返しします")
}
return isBuyable
case .energyDrink:
let isBuyable = validateDrinkBuyable(with: energyDrink, inputYen: inputedYen, change: calculate.change)
if isBuyable {
reduceStock(type: type)
calculate.calculateChange(money: calculate.inputedYen, price: DrinkType.energyDrink.price)
print("\(type)をどうぞ!\(calculate.change)円のお返しです!")
}else if energyDrink.stock == .zero{
print("すみません在庫切れです!\(calculate.inputedYen)円お返しします")
}else{
print("お金が足りません!\(calculate.inputedYen)円お返しします")
}
return isBuyable
}
}
// 在庫を減らす関数
func reduceStock(type: DrinkType) {
switch type {
case .coffee: coffee.stock -= 1
case .water: water.stock -= 1
case .energyDrink: energyDrink.stock -= 1
}
}
}
let activateVendingMachine = VendingMachine()
//type引数にドリンク名を入れる
activateVendingMachine.buyDrink(type: .coffee, inputedYen: calculate.inputedYen)
#おわりに
反省点として、
・実装予定だった釣り銭切れの機能が実装できなかった
・ドリンクとお金の入力箇所が離れている
・キレイにまとめるつもりが、結果的に可読性の低いコードになってしまった
釣り銭切れの機能は、釣り銭が100円だとしたら、
100円1枚、50円2枚、10円10枚、10円5枚と50円1枚と、100円だけでも4パターン考えられ、1000円以上入力されるとなると、全てのパターンを網羅する必要があり、良い方法を見つけることができなかったので諦めました…泣
また、ユーザーの入力箇所が離れているのと、コードが冗長で、可読性が低いなと感じました。
他にも、汎用性や拡張性はどうなの?とか考えているとキリがないですし、今回のビジネスロジック実装でとても勉強になったので、気を取り直して次の課題に進みます!