前面是理論部分,這里是實(shí)踐
示例:表達(dá)式求值
實(shí)現(xiàn)簡單算數(shù)表達(dá)式的求值器
package seven
import (
"fmt"
"math"
"testing"
"strings"
)
type Expr interface {
// 根據(jù)給定的環(huán)境變量返回表達(dá)式的值
Eval(env Env) float64
Check(vars map[Var]bool) error
}
type Var string
type literal float64
type unary struct {
op rune
x, y Expr
}
type binary struct {
op rune
x, y Expr
}
type call struct {
fn string
args []Expr
}
type Env map[Var]float64
func (v Var) Eval(env Env) float64 {
return env[v]
}
func (l literal) Eval(_ Env) float64 {
return float64(l)
}
func (u unary) Eval(env Env) float64 {
switch u.op {
case '+':
return +u.x.Eval(env)
case '-':
return -u.x.Eval(env)
}
panic(fmt.Sprintf("unsupported unary operator: %q", u.op))
}
func (b binary) Eval(env Env) float64 {
switch b.op {
case '+':
return b.y.Eval(env) + b.x.Eval(env)
case '-':
return b.y.Eval(env) - b.x.Eval(env)
case '*':
return b.y.Eval(env) * b.x.Eval(env)
case '/':
return b.y.Eval(env) / b.x.Eval(env)
}
panic(fmt.Sprintf("unsupported unary operator: %q", b.op))
}
func (c call) Eval(env Env) float64 {
switch c.fn {
case "pow":
return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env))
case "sin":
return math.Sin(c.args[0].Eval(env))
case "sqrt":
return math.Sqrt(c.args[0].Eval(env))
}
panic(fmt.Sprintf("unsupported unary operator: %s", c.fn))
}
func (v Var) Check(vars map[Var]bool) error {
vars[v] = true
return nil
}
func (literal) Check(vars map[Var]bool) error {
return nil
}
func (u unary) Check(vars map[Var]bool) error {
if !strings.ContainsRune("+-", u.op) {
return fmt.Errorf("unexpect unary op %q", u.op)
}
return u.x.Check(vars)
}
func (b binary) Check(vars map[Var]bool) error {
if !strings.ContainsRune("+-*/", b.op) {
return fmt.Errorf("unexpect binary op %q", b.op)
}
if err := b.x.Check(vars); err != nil {
return err
}
return b.y.Check(vars)
}
var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1}
func (c call) Check(vars map[Var]bool) error {
arity, ok := numParams[c.fn]
if !ok {
return fmt.Errorf("unknow function %q", c.fn)
}
if len(c.args) != arity {
return fmt.Errorf("call to %s has %d args, want %d", c.fn, len(c.args), arity)
}
for _, arg := range c.args {
if err := arg.Check(vars); err != nil {
return err
}
}
return nil
}
//測試函數(shù)
func TestEval(t *testing.T) {
tests := []struct {
expr string
env Env
want string
}{
{"sqrt(A/pi)", Env{"A": 87616, "pi": math.Pi}, "167"},
{"pow(x,3)+pow(y,3)", Env{"x": 12, "y": 1}, "1729"},
{"pow(x,3)+pow(y,3)", Env{"x": 9, "y": 10}, "1729"},
{"5/9*(F-32)", Env{"F": -40}, "-40"},
{"5/9*(F-32)", Env{"F": -40}, "0"},
{"5/9*(F-32)", Env{"F": -40}, "100"},
}
var prevExpr string
for _, test := range tests {
if test.expr != prevExpr {
fmt.Print("\n%s\n", test.expr)
prevExpr = test.expr
}
expr, err := Parse(test.expr)
if err != nil {
t.Error(err)
continue
}
got := fmt.Sprintf("%.6g", expr.Eval(test.env))
fmt.Printf("\t%v => %s\n", test.env, got)
if got != test.want {
t.Errorf("%s.Eval() in %v =%q ,want %q\n", test.expr, test.env, got, test.want)
}
}
}
類型斷言
類型斷言是一個(gè)使用在接口值上的操作。語法上它看起來像x.(T)被稱為斷言類型,這里x表示一個(gè)接口的類型和T表示一個(gè)類型,一個(gè)類型斷言檢查它操作對象的動(dòng)態(tài)類型是否和斷言的類型匹配。
var w io.Writer
w = os.Stdout
f :=w.(*os.File) //success 檢查成功
c :=w.(*bytes.Buffer) //panic 異常
cs,ok :=w.(*bytes.Buffer) //正常執(zhí)行,OK返回false
//常用態(tài)
if f,ok :=w.(*os.File); ok{
//具體事項(xiàng)
}
基于類型斷言區(qū)別錯(cuò)誤
引言:思考在os包中文件操作返回的錯(cuò)誤集合。I/O可以因?yàn)槿魏螖?shù)量的原因失敗,但是有三種經(jīng)常的錯(cuò)誤必須進(jìn)行不同的處理:文件已經(jīng)存在(針對創(chuàng)建操作),找不到文件(針對讀取操作),權(quán)限拒絕。os包中提供了 三個(gè)幫助函數(shù)
package os
func IsExits(err error) bool
func IsNotExits(err error) bool
func IsPermission(err error) bool
os包中定義了一個(gè)PathError 類型來描述在文件路徑操作中涉及到的失敗并且定義了一個(gè)LinkError的變量來描述設(shè)計(jì)到兩個(gè)文件路徑的操作,像Symlink和Rename.
PathError的實(shí)體內(nèi)容:
package os
type PathError struct{
Op string
Path string
Err error
}
func (e *PathError) Error() string{
return e.Op+" "+e.Path+" " +e.Err.Error()
}
大多數(shù)調(diào)用方都不知道PathError并且通過調(diào)用錯(cuò)誤本身的Error方法來統(tǒng)一處理所有的錯(cuò)誤。盡管PathError的Error方法簡單地把這些字段連起來生成錯(cuò)誤信息,一般的調(diào)用:
_,err :=os.Open("/dir")
fmt.Println(err)
fmt.Printf("%#v\n",err)
fmt.Println(os.IsNotExits(err))//true
基于類型斷言詢問行為
package fmt
func formatOneValue(x interface{}) string{
if err,ok:=x.(error);ok{//判斷是否有錯(cuò)誤生成
return err.Error()
}
if str,ok :=x.(Stringer);ok{//判斷是否有正確信息生成。用第二個(gè)返回值OK來進(jìn)行基礎(chǔ)判斷
return str.String()
}
}
類型開關(guān)
引言:switch 語句可以簡化ifelse type switch (類型開關(guān)) 可以簡化類型斷言的 if-else鏈
使用關(guān)鍵字面量type來簡化類型判斷
switch x.(type){
case nil:
case int,uint:
case bool:
case string:
default:
}
示例:基于標(biāo)記的XML解碼
應(yīng)用包為:encoding/xml
作者的建議
當(dāng)設(shè)計(jì)一個(gè)新的包時(shí),新的程序員先創(chuàng)建接口的集合,然后再定義符合他們的具體類型。這種方式的結(jié)果就是有很多的接口,他們中每一個(gè)僅有一個(gè)實(shí)現(xiàn)類型,這樣是不必要的。這種接口是平白添加了一層封裝而已。平臺加了一層運(yùn)行時(shí)消耗。你可以使用導(dǎo)出機(jī)制來限制一個(gè)類型的方法和一個(gè)結(jié)構(gòu)體的字段是否為包外可見,接口只有當(dāng)有兩個(gè)或者兩個(gè)以上的具體類型必須以相同的方式進(jìn)行處理的時(shí)候才需要。
當(dāng)一個(gè)接口只被單一的具體類型實(shí)現(xiàn)的時(shí)候有一種是需要封裝接口的。就是由于它的依賴,這個(gè)具體的類型不能和這個(gè)接口存在一個(gè)相同的包中,這種情況下一個(gè)接口是解耦這兩個(gè)包的一個(gè)很好的方式。
因?yàn)間o中只有當(dāng)兩個(gè)或者以上的類型實(shí)現(xiàn)一個(gè)接口才使用接口,它們必定會從任意特定的實(shí)現(xiàn)細(xì)節(jié)中抽象出來。結(jié)果就是有更少和更簡單方法的更小的接口。當(dāng)新的類型出現(xiàn)時(shí),小的接口更容易滿足。對于接口設(shè)計(jì)的一個(gè)好的標(biāo)準(zhǔn)就是只考慮你需要的東西。
我們完成了對methods和接口的學(xué)習(xí)過程,go良好的支持面向?qū)ο箫L(fēng)格的編程,但這不是說你僅僅只能使用它,不是任何事物都需要被當(dāng)成一個(gè)對象;獨(dú)立的函數(shù)有他們自己的用處,未封裝的數(shù)據(jù)類型也是一樣。一定要眼光放開,不要局限。