Go语言教程之边写边学:编写并测试程序 :完成银行项目功能

你的程序已具备一些基本功能。 但是,它还缺少一项功能:向其他帐户转账的功能。 这一挑战包括添加该功能,以及我们认为可为现有API增加价值的另一项功能。

实现转账方法

若要创建转账方法,应牢记以下几点:

  • 你需要实现向其他帐户转账的功能。 在本例中,你必须使用至少两个帐户来初始化程序,而不是像之前一样只使用一个帐户。
  • 由于你要在核心程序包中添加新方法,因此请首先创建测试用例,以确保你编写正确的逻辑来进行转账。 请密切注意在函数与指针之间进行通信的方式。
  • 你的转账方法应当接收你要转账的金额以及你将在其中增加资金的帐户对象。 请确保重用存款和取款方法以避免重复(特别是对于错误处理)。
  • 请记住,如果你没有足够的资金,则无法向其他帐户转账。

修改对账单终结点以返回JSON对象

当前,/statement终结点会返回一个字符串。如果你想将其公开为API,则该字符串没有用处。 修改终结点以采用JSON格式返回帐户对象:
"{\"Name\":\"John\",\"Address\":\"Los Angeles, California\",\"Phone\":\"(213) 555 0147\",\"Number\":1001,\"Balance\":0}"
我们希望你假定使用你的核心程序包的用户有可能希望实施不同的对账单方法来更改输出。 因此,你需要进行适当的更改,以使核心程序包具有可扩展性。 换句话说,你需要执行以下操作:
创建一个包含Statement() string函数的接口。
在核心程序包中创建一个新的Statement() 函数,该函数接收你以参数形式创建的接口。 此函数应该调用你的结构已有的Statement() 方法。
当你进行这些更改时,系统会允许你创建自定义Account结构和自定义Statement() 方法。 如果不记得如何执行这些编码更改,你可以返回到有关结构(嵌入)和接口的模块。

下面是解决此模块中的两项挑战的完整代码。
银行核心测试:bank_test.go
//...

func TestTransfer(t *testing.T) {
    accountA := Account{
		Customer: Customer{
			Name:    "John",
			Address: "Los Angeles, California",
			Phone:   "(213) 555 0147",
		},
		Number:  1001,
		Balance: 0,
	}

	accountB := Account{
		Customer: Customer{
			Name:    "Mark",
			Address: "Irvine, California",
			Phone:   "(949) 555 0198",
		},
		Number:  1002,
		Balance: 0,
	}

	accountA.Deposit(100)
	err := accountA.Transfer(50, &accountB)

	if accountA.Balance != 50 && accountB.Balance != 50 {
		t.Error("transfer from account A to account B is not working", err)
	}
}

银行核心:bank.go

package bank

import (
	"errors"
	"fmt"
)

//...

// Transfer function
func (a *Account) Transfer(amount float64, dest *Account) error {
	if amount <= 0 {
		return errors.New("the amount to transfer should be greater than zero")
	}

	if a.Balance < amount {
		return errors.New("the amount to transfer should be greater than the account's balance")
	}

	a.Withdraw(amount)
	dest.Deposit(amount)
	return nil
}

// Bank ...
type Bank interface {
	Statement() string
}

// Statement ...
func Statement(b Bank) string {
	return b.Statement()
}

银行API:main.go

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"

	"github.com/msft/bank"
)

var accounts = map[float64]*CustomAccount{}

func main() {
	accounts[1001] = &CustomAccount{
		Account: &bank.Account{
			Customer: bank.Customer{
				Name:    "John",
				Address: "Los Angeles, California",
				Phone:   "(213) 555 0147",
			},
			Number: 1001,
		},
	}

	accounts[1002] = &CustomAccount{
		Account: &bank.Account{
			Customer: bank.Customer{
				Name:    "Mark",
				Address: "Irvine, California",
				Phone:   "(949) 555 0198",
			},
			Number: 1002,
		},
	}

	http.HandleFunc("/statement", statement)
	http.HandleFunc("/deposit", deposit)
	http.HandleFunc("/withdraw", withdraw)
	http.HandleFunc("/transfer", transfer)
	log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

//...

func transfer(w http.ResponseWriter, req *http.Request) {
	numberqs := req.URL.Query().Get("number")
	amountqs := req.URL.Query().Get("amount")
	destqs := req.URL.Query().Get("dest")

	if numberqs == "" {
		fmt.Fprintf(w, "Account number is missing!")
		return
	}

	if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
		fmt.Fprintf(w, "Invalid account number!")
	} else if amount, err := strconv.ParseFloat(amountqs, 64); err != nil {
		fmt.Fprintf(w, "Invalid amount number!")
	} else if dest, err := strconv.ParseFloat(destqs, 64); err != nil {
		fmt.Fprintf(w, "Invalid account destination number!")
	} else {
		if accountA, ok := accounts[number]; !ok {
			fmt.Fprintf(w, "Account with number %v can't be found!", number)
		} else if accountB, ok := accounts[dest]; !ok {
			fmt.Fprintf(w, "Account with number %v can't be found!", dest)
		} else {
			err := accountA.Transfer(amount, accountB.Account)
			if err != nil {
				fmt.Fprintf(w, "%v", err)
			} else {
				fmt.Fprintf(w, accountA.Statement())
			}
		}
	}
}

func statement(w http.ResponseWriter, req *http.Request) {
	numberqs := req.URL.Query().Get("number")

	if numberqs == "" {
		fmt.Fprintf(w, "Account number is missing!")
		return
	}

	number, err := strconv.ParseFloat(numberqs, 64)
	if err != nil {
		fmt.Fprintf(w, "Invalid account number!")
	} else {
		account, ok := accounts[number]
		if !ok {
			fmt.Fprintf(w, "Account with number %v can't be found!", number)
		} else {
			json.NewEncoder(w).Encode(bank.Statement(account))
		}
	}
}

// CustomAccount ...
type CustomAccount struct {
	*bank.Account
}

// Statement ...
func (c *CustomAccount) Statement() string {
	json, err := json.Marshal(c)
	if err != nil {
		return err.Error()
	}

	return string(json)
}