如何区分玩家现金及其操纵之间的问题?

How to separate concerns between a players cash and its manipulation?

在一个非常小的游戏中,我正在写一个 player 有一些现金(这只是一个 Int),但我不想直接操纵 [=13] 中的现金=] class;我觉得玩家 class 不应该关心现金操纵。

因此,我将所有现金操作转移到另一个 class。

这就是我感到困惑的地方。我正在阅读 VIPER 和其他模式,其中 "walled gardens" 意味着 class 通常不知道其父对象或无法直接访问另一个对象。

所以,说到这里,我不确定我是否打算将玩家的现金和 credit/debit 现金存入玩家钱包的能力区分开来。

在我的现金处理程序中,我需要做一些错误检查,throw 错误等等;现在这对我的问题并不重要;因此我删除了这些以专注于我的问题的主要内容。

我正在使用 Swift Playgrounds 来解决我对如何解决这个小问题的 2 个主要想法。

// : Idea #1 -- Use static functions

class Player {
    var cash: Int = 0
}
let p = Player.init()


struct Wallet{
    static func credit(account: Player, amount: Int) {
        // (#TODO) the final one will do some checks, throw an error
        var balance: Int = account.cash
        balance += amount
        account.cash = balance
    }
    static func debit(account: Player, amount: Int)  {
        // (#TODO) the final one will do some checks, throw an error
        var balance: Int = account.cash
        balance -= amount
        account.cash = balance
    }
}
Wallet.credit(account: p, amount: 125)
Wallet.debit(account: p, amount: 25)
print (p.cash)

好的,在这一个中我使用静态函数;但是 Wallet 结构可以直接访问播放器。我觉得这是错误的。

我的第二次尝试:

class Player {
    var account: Account = Account()

    var cash: Int {
        return account.balance
    }

    init(cash: Int) {
        self.account = Account(openingBalance: cash)
    }
}

var p = Player.init(cash: 50)

class Account {
    public private(set) var balance: Int = 0

    init(openingBalance: Int = 0) {
        self.balance = openingBalance
    }

    func credit(amount: Int) -> Int {
        balance += amount
        return self.balance
    }

    func deposit(amount: Int) -> Int {
        balance -= amount
        return self.balance
    }
}

p.account.credit(amount: 100)
print (p.cash)

这个感觉更干净,但现在 Player 对象可以直接访问 Account?


编辑:我有第三次尝试。我看到了一个叫做 proxy design pattern 的东西,如果我不熟悉这个模式,抱歉;据我了解,你可以在玩家和银行账户之间设置一些东西作为代理来决定玩家是否可以贷记或借记他们的账户。

遗憾的是,这个实验不太奏效;我现在有一个看似无穷无尽的 do-catch 语句循环;我不确定如何将其踢回主程序。

// : Third attempt -- I think this follows a Proxy pattern

public enum CashError: Error, Equatable {
    case mustBePositive
    case notEnoughFunds
    case cannotPerformTransaction
    case amountWouldBeNegative
}

class Bank {
    var balance: Int = 0

    enum TransactionType: Int {
        case credit = 0
        case debit
    }

    func performTransaction(transactionType: TransactionType, amount: Int) {
        switch transactionType {
        case .credit:
            self.credit(amount: amount)
        case .debit:
            self.debit(amount: amount)
        }
    }

    private func credit(amount: Int = 0) {
        print ("didCredit: \(amount)")
        self.balance += amount
    }

    private func debit(amount: Int = 0) {
        print ("didDebit: \(amount)")
        self.balance -= amount
    }
}

class Customer {
    private(set) var accountProxy: AccountProxy?

    var cash: Int {
        guard let proxy: AccountProxy = accountProxy else {
            return 0
        }
        return proxy.balance
    }

    init(cash: Int = 0) {
        print ("Create player with $\(cash)")

        self.accountProxy = AccountProxy.init(customer: self)

        guard let proxy = self.accountProxy else {
            return
        }

        do {
            let _ = try proxy.handle(transactionType: .credit, amount: cash)
        } catch {
            print (error)
        }
    }
}

class AccountProxy {
    private var bank: Bank = Bank()
    private var customer: Customer
    public var balance: Int {
        return self.bank.balance
    }

    init(customer: Customer) {
        self.customer = customer
    }

    func handle(transactionType: Bank.TransactionType, amount: Int = 0) throws -> Bool {
        print ("Attempting \(transactionType) of $\(amount)")

        do {
            if let _ = try canPerformTransaction(transactionType: transactionType, amount: amount) {
                print ("proxy: says you can \(transactionType): $\(amount)")
                self.bank.performTransaction(transactionType: transactionType, amount: amount)
                return true
            }
            else {
                print ("proxy: error - Cannot perform transction")
                throw CashError.cannotPerformTransaction
            }
        } catch {
            throw (error)
        }
    }

    // (Private) functions

    private func canPerformTransaction(transactionType: Bank.TransactionType, amount: Int ) throws -> Bool? {
        switch transactionType {
        case .credit:
            do {
                guard let result = try canCredit(amount: amount) else {
                    return false
                }
                return result
            } catch {
                throw error
            }

        case .debit:
            do {
                guard let result = try canDebit(amount: amount) else {
                    return false
                }
                return result
            } catch {
                throw error
            }
        }
    }

    private func canCredit(amount: Int) throws -> Bool? {
        guard amount >= 0 else {
            throw CashError.mustBePositive
        }
        return true
    }

    private func canDebit(amount: Int) throws -> Bool? {
        // amount must be > 0
        guard amount > 0 else {
           throw CashError.mustBePositive
       }
        // balance must be >= amount
       guard balance >= amount else {
           throw CashError.notEnoughFunds
       }
        // the remaining sum must be >= 0
       let sum = balance
       guard ((sum - amount) >= 0) else {
           throw CashError.amountWouldBeNegative
       }
        return true
    }
}


let bob = Customer.init(cash: 100)
print ("Bob has $\(bob.cash)")
do {
    let _ = try bob.accountProxy?.handle(transactionType: .credit, amount: 125)
} catch {
    print (error)
}
print ("Bob has $\(bob.cash)")
do {
    let _ = try bob.accountProxy?.handle(transactionType: .debit, amount: 25)
} catch {
    print (error)
}
print ("Bob has $\(bob.cash)")

// (Logged Output):
// Create player with 0
// Attempting credit of 0
// proxy: says you can credit: 0
// didCredit: 100
// Bob has 0
// Attempting credit of 5
// proxy: says you can credit: 5
// didCredit: 125
// Bob has 5
// Attempting debit of 
// proxy: says you can debit: 
// didDebit: 25
// Bob has 0

所以我的查询是基于"walled gardens"的概念?玩家 class 应该知道其帐户吗?

如果这看起来很明显,我深表歉意,我觉得非常 frustrating/confusing。

非常感谢feedback/assistance。

当然对于这么小的例子,分离关注点可能不是很有用,但我会继续......

class Player {
    let wallet: Wallet
    // no reference to cash here.
}

class Wallet {
    private var cash: Int
    // methods manipulating cash go here.
}

现在玩家不需要任何围绕操纵 cash 变量的代码,但这意味着玩家必须公开其钱包以便其他人可以存入和取出现金.这分离了关注点但打破了遏制。玩家 object 无法再控制谁或什么从其钱包中取出现金,考虑到现实世界中这些事情的例子,这听起来像是一个相当糟糕的主意。

您可以在 Player 中将电子钱包设为私有,但这样做会使电子钱包变得多余。当然,除非您不信任 Player 以确保其 cash 不会变为负数。

use-cases 将决定可能的适当关系。我最喜欢的例子是关于汽车及其引擎的。对于 "startCar" 用例,明显的关系是包含 [Car]<>-->[Engine]。当 driver 想要启动汽车时,它根本不关心引擎的内部结构。但是,对于 "change oil" 用例,最好的关系是关联 [Car]--->[Engine],因为机械师希望能够直接接触发动机,而不必每次都将发动机从汽车中取出并更换他需要做出改变。

我希望这些杂乱无章的内容对您有所帮助...