Examples

现在我们知道如何创建自定义类型,在这里我们准备看一个更现实和完整的例子。第一个例子向我们展示了如何创建一个简单的自定义类型的。第二个例子向我们展示了如何如何使用嵌入的interface和structs, 以及一个用于创建包中所有类型的工厂函数。第三个例子是实现一个完整的自定义通用集合类型。

Example: FuzzyBool

在这个例子中,我们将看到如何创建一个单值的自定义类型以及它支持的方法.源文件在fuzzy/fuzzybool/fuzzybool.go.

内置的bool类型都是两值的(true and false). 但是在人工智能领域里,需要使用到fuzzy Booleans. 这种类型的值对应于"true"和"false",以及这两者之间。在我们的实现在,我们将使用floating-potin值。0.0表示false, 1.0表示true. 在这种系统下,0.5表示50% true(50% false), 0.25表示25%true(75%),等等。以下是这种类型的使用以及它们产生的结果.

func main() {
    a, _ := fuzzybool.New(0) // Safe to ignore err value when using
    b, _ := fuzzybool.New(.25) // known valid values; must check if using
    c, _ := fuzzybool.New(.75) // variables though.
    d := c.Copy()
    if err := d.Set(1); err != nil {
    fmt.Println(err)
    }
    process(a, b, c, d)
    s := []*fuzzybool.FuzzyBool{a, b, c, d}
    fmt.Println(s)
}
func process(a, b, c, d *fuzzybool.FuzzyBool) {
    fmt.Println("Original:", a, b, c, d)
    fmt.Println("Not: ", a.Not(), b.Not(), c.Not(), d.Not())
    fmt.Println("Not Not: ", a.Not().Not(), b.Not().Not(), c.Not().Not(),
    d.Not().Not())
    fmt.Print("0.And(.25)→", a.And(b), "• .25.And(.75)→", b.And(c),
    "• .75.And(1)→", c.And(d), " • .25.And(.75,1)→", b.And(c, d), "\n")
    fmt.Print("0.Or(.25)→", a.Or(b), "• .25.Or(.75)→", b.Or(c),
    "• .75.Or(1)→", c.Or(d), " • .25.Or(.75,1)→", b.Or(c, d), "\n")
    fmt.Println("a < c, a == c, a > c:", a.Less(c), a.Equal(c), c.Less(a))
    fmt.Println("Bool: ", a.Bool(), b.Bool(), c.Bool(), d.Bool())
    fmt.Println("Float: ", a.Float(), b.Float(), c.Float(), d.Float())
}
/*
Original: 0% 25% 75% 100%
Not: 100% 75% 25% 0%
Not Not: 0% 25% 75% 100%
0.And(.25)→0% .25.And(.75)→25% .75.And(1)→75% 0.And(.25,.75,1)→0%
0.Or(.25)→25% .25.Or(.75)→75% .75.Or(1)→100% 0.Or(.25,.75,1)→100%
a < c, a == c, a > c: true false false
Bool: false false true true
Float: 0 0.25 0.75 1
[0% 25% 75% 100%]
*/

这个自定义类型我们称为FuzzyBool, 我们将先看看这个类型的定义以及构造函数。

type FuzzyBool struct{ value float32 }

FuzzyBool类型是基于一个struct, 这个struct包含了单个float32. 这个value是非导出的,所以任何人导入了fuzzyBool package都必须使用构造函数创建,这样就可以保证创建的fuzzy类型的布尔值,都是有效的值(比如,不能为负数)。

由于FuzzyBool类型是基于一个struct, 而它又只有单个值,因此我们可以将这个定义简化为 FuzzyBool struct{float32}. 但这会改变这个值的访问——将从fuzzy.value改变为fuzzy.float32. 我们更愿意使用一个已命名的变量,第一是因为这种方式更美观,第二是因为我们在改变底层的类型(比如,float32改变为float64)时,不需要做更多的修改。

因为struct只有一个值,所以更进一步的变化也有可能。比如我们可以将这个类型改变为 FuzzyBool float32, 让它直接基于float32. 虽然这样也可以,但相对于struct,需要更多的代码和技巧去实现相同的功能。但是,如果我们想要创建一个不可变的fuzzy Booleans(唯一的区别在于不是用Set()方法设置一个新的值,而是赋值一个新的fuzzy Boolean值给变量,比如上面例子中的d),我们就可以大大的简化这段代码,让它直接基于float32.

func New(value interface{}) (*FuzzyBool, error) {
    amount, err := float32ForValue(value)
    return &FuzzyBool{amount}, err
}

为了方便fuzzy Boolean的使用,在构造函数中不仅可以接受float32类型的参数,也可以接受float64(Go默认的浮点类型),int(默认的整型类型),以及bools. 这种灵活性是通过自定义的float32ForValue()函数实现的,它返回一个float32 和 nil——或都返回0.0和一个error.如果这个值不能被处理的话。

如果我们传递的值是一个无效类型,我们就制造了一个编程错误,但我们又不想应用程序在我们的用户那里发生崩溃。 所以我们不能仅仅返回一个*FuzzyBool, 我们还需要返回一个error. 如果我们传递值是一个已知有效的字面量(literal)给New()方法,我们可以忽略这个error; 但如果我们传递一个变量,我们就必须检查返回是error是否为nil.

New()函数返回一个指针,而不是值,是因为我们想让fuzzy Boolean变得可变。这个意思是它的方法会修改fuzzy Boolean的值(在这个例子中,只有Set()会修改这个值),所以这些方法的接受者为指针,而不是值。

一个经验法则是,为不变的类型创建的方法接收者为值,而可变的类型,接收者指针(对于可变类型,有的方法接受者为值,有的为指针),最好还是用指针传递大的struct类型(两个或者多个字段)。

func float32ForValue(value interface{}) (fuzzy float32, err error) {
    switch value := value.(type) { // shadow variable
    case float32:
        fuzzy = value
    case float64:
        fuzzy = float32(value)
    case int:
        fuzzy = float32(value)
    case bool:
        fuzzy = 0
        if value {
            fuzzy = 1
        }
    default:
        return 0, fmt.Errorf("float32ForValue(): %v is not a "+
            "number or Boolean", value)
    }
    if fuzzy < 0 {
        fuzzy = 0
    } else if fuzzy > 1 {
        fuzzy = 1
    }
    return fuzzy, nil
}

这个非导出的辅助函数被用于New()和Set()方法,它将一个值转换为[0.0, 1.0]范围内的 float32。

如果这个函数处理一个无效的类型,我们将返回一个non-nil error. 这就让他的调用者有能力检查返回的值,并且进行处理。这个调用者在错误发生时,触发一个panic异常,引用就应用程序崩溃,并对错误消息进行追踪。 或者自已处理错误。对于低级别的函数最好的方法是像这个函数一样,当遇到错误时,返回一个error. 因为它们不能充分的了解程序的逻辑,就不知道如何或者是否需要处理错误,所以将问题丢给它的调用者,在调用者那应该能够更好的知道要做什么。

//fuzzy.go
package main

import (
    "fmt"
    "fuzzy/fuzzybool"
)

func main() {
    a, _ := fuzzybool.New(0)   // Safe to ignore err value when using
    b, _ := fuzzybool.New(.25) // known valid values; must check if using
    c, _ := fuzzybool.New(.75) // variables though.
    d := c.Copy()
    if err := d.Set(1); err != nil {
        fmt.Println(err)
    }
    process(a, b, c, d)
    s := []*fuzzybool.FuzzyBool{a, b, c, d}
    fmt.Println(s)
}

func process(a, b, c, d *fuzzybool.FuzzyBool) {
    fmt.Println("Original:", a, b, c, d)
    fmt.Println("Not:     ", a.Not(), b.Not(), c.Not(), d.Not())
    fmt.Println("Not Not: ", a.Not().Not(), b.Not().Not(), c.Not().Not(),
        d.Not().Not())
    fmt.Print("0.And(.25)→", a.And(b), "• .25.And(.75)→", b.And(c),
        "• .75.And(1)→", c.And(d), " • .25.And(.75,1)→", b.And(c, d), "\n")
    fmt.Print("0.Or(.25)→", a.Or(b), "• .25.Or(.75)→", b.Or(c),
        "• .75.Or(1)→", c.Or(d), " • .25.Or(.75,1)→", b.Or(c, d), "\n")
    fmt.Println("a < c, a == c, a > c:", a.Less(c), a.Equal(c), c.Less(a))
    fmt.Println("Bool:    ", a.Bool(), b.Bool(), c.Bool(), d.Bool())
    fmt.Println("Float:   ", a.Float(), b.Float(), c.Float(), d.Float())
}
//fuzzybool
package fuzzybool

import "fmt"

type FuzzyBool struct{ value float32 }

func New(value interface{}) (*FuzzyBool, error) {
    amount, err := float32ForValue(value)
    return &FuzzyBool{amount}, err
}

func float32ForValue(value interface{}) (fuzzy float32, err error) {
    switch value := value.(type) { // shadow variable
    case float32:
        fuzzy = value
    case float64:
        fuzzy = float32(value)
    case int:
        fuzzy = float32(value)
    case bool:
        fuzzy = 0
        if value {
            fuzzy = 1
        }
    default:
        return 0, fmt.Errorf("float32ForValue(): %v is not a "+
            "number or Boolean", value)
    }
    if fuzzy < 0 {
        fuzzy = 0
    } else if fuzzy > 1 {
        fuzzy = 1
    }
    return fuzzy, nil
}

func (fuzzy *FuzzyBool) Set(value interface{}) (err error) {
    fuzzy.value, err = float32ForValue(value)
    return err
}

func (fuzzy *FuzzyBool) Copy() *FuzzyBool {
    return &FuzzyBool{fuzzy.value}
}

func (fuzzy *FuzzyBool) String() string {
    return fmt.Sprintf("%.0f%%", 100*fuzzy.value)
}

func (fuzzy *FuzzyBool) Not() *FuzzyBool {
    return &FuzzyBool{1 - fuzzy.value}
}

func (fuzzy *FuzzyBool) And(first *FuzzyBool,
    rest ...*FuzzyBool) *FuzzyBool {
    minimum := fuzzy.value
    rest = append(rest, first)
    for _, other := range rest {
        if minimum > other.value {
            minimum = other.value
        }
    }
    return &FuzzyBool{minimum}
}

func (fuzzy *FuzzyBool) Or(first *FuzzyBool,
    rest ...*FuzzyBool) *FuzzyBool {
    maximum := fuzzy.value
    rest = append(rest, first)
    for _, other := range rest {
        if maximum < other.value {
            maximum = other.value
        }
    }
    return &FuzzyBool{maximum}
}

func (fuzzy *FuzzyBool) Less(other *FuzzyBool) bool {
    return fuzzy.value < other.value
}

func (fuzzy *FuzzyBool) Equal(other *FuzzyBool) bool {
    return fuzzy.value == other.value
}

func (fuzzy *FuzzyBool) Bool() bool {
    return fuzzy.value >= .5
}

func (fuzzy *FuzzyBool) Float() float64 {
    return float64(fuzzy.value)
}