接口(2)
- 一个非空接口自身并没有什么用处,为了让它发挥作用,我们必须创建一些自定义的类型,其中定义了一些接口所需的方法
- 两个自定义类型的例子:
type StringPair struct { first, second string }
func (pair *StringPair) Exchange() {
pair.first, pair.second = pair.second, pair.first
}
type Point [2]int
func (point *Point) Exchange() { point[0], point[1] = point[1], point[0] }
- 方法的接收者声明为指向其类型的指针,以便我们可以修改调用该方法的(指针所指向的)值
- 我们完成一个满足签名
String() string
的方法:
func (pair StringPair) String() string {
return fmt.Sprintf("%q + %q", pair.first, pair.second)
}
- 通常而言,除了小数据外,我们更倾向于使用指针接收者,因为传指针比传值更高效
接口嵌入
- 接口可以嵌入其他接口,其效果与在接口中直接被嵌入接口的方法一样,例子:
type LowerCaser interface {
LowerCase()
}
type UpperCaser interface {
UpperCase()
}
type LowerUpperCaser interface {
LowerCaser
UpperCaser
}
-
LowerCaser
接口声明了一个方法LowerCase()
,它不接受参数,也没有返回值,UpperCaser
接口也类似 - 而
LowerUpperCaser
接口则将这两个接口嵌套进来,如果要满足它,这需要定义LowerCase()
和UpperCase()
- 嵌入可能看起来没多大的优势,但是如果需要添加两个接口添加额外的方法,例子:
type FixCaser interface {
FixCase()
}
type ChangeCaser interface {
LowerUpperCaser
FixCaser
}
-
我们可以得到一个分等级的嵌套接口,如图:
这些接口并没有多大的用处,我们需要定义具体的类型来实现它们:
func (part *Part) FixCase() {
part.Name = fixCase(part.Name)
}
func fixCase(s string) string {
var chars []rune
upper := true
for _, char := range s {
if upper {
char = unicode.ToUpper(char)
} else {
char = unicode.ToLower(char)
}
chars = append(chars, char)
upper = unicode.IsSpace(char) || unicode.Is(unicode.Hyphen, char)
}
return string(chars)
}
func (pair *StringPair) UpperCase() {
pair.first = strings.ToUpper(pair.first)
pair.second = strings.ToUpper(pair.second)
}
func (pair *StringPair) FixCase() {
pair.first = fixCase(pair.first)
pair.second = fixCase(pair.second)
}
- 接口的灵活性的另一方面是,它们可以在事后创建,例子:
type IsValider interface {
IsValid() bool
}
if thing, ok := x.(IsValider); ok {
if !thing.IsValid(){
reportInvalid(thing)
} else {
//...处理有效的thing...
}
}
- 创建了接口之后,我们可以检查任意自定义类型看它是否提供
IsValid() bool
方法 - 接口提供了一种高度抽象的机制,当某些函数或者方法只关心该传入的值能完成使命工作,而不关心该值的实际类型,接口就允许我们声明一个方法的集合