[golang note] 接口使用
侵入式接口
√ 在其他一些编程语言中,接口主要是作为不同组件之间的契约存在,即规定双方交互的规约。
√ 对契约的实现是强制的,即必须确保用户的确实现了该接口,而实现一个接口,需要从该接口继承。
√ 如果一个类实现了接口A,即便另一个接口B与A的方法列表相同,甚至连接口名都相同,但位于不同的命名空间下,那么编译器并不认为这两个接口是一样的。
√ 所谓侵入的主要表现在于实现继承接口的类需要明确声明自己实现自某个接口。
√ 侵入式接口常纠结的问题是:应该提供哪些接口好呢?如果两个类实现了相同的接口,应该把接口放到哪个包好呢?
• c++接口示例
#include <string> #include <iostream> using namespace std; class IPerson { public: IPerson() {} virtual ~IPerson() {} public: virtual void SetName(const string &name) = 0; virtual const string &GetName() = 0; virtual void Work() = 0; }; class Teacher : public IPerson { private: string m_Name; public: Teacher() : m_Name("") {} virtual ~Teacher() {} public: virtual void SetName(const string &name) { m_Name = name; } virtual const string &GetName() { return m_Name; } virtual void Work() { cout << "I'm a teacher" << endl; } }; int main() { IPerson *p = ::new Teacher; p->SetName("chris"); p->Work(); cout << p->GetName() << endl;
delete p; return 1; }
• java接口示例
非侵入式接口
√ 在golang中,一个类只需要实现了某个接口中的所有函数,我们就说这个类实现了该接口。
√ 在golang中,一个类不需要继承于接口,也不需要知道有哪些接口的存在。
√ 在golang中,接口和类之间不再有所谓的契约关系,因此实现类的时候,只需要关心自己应该提供哪些方法,不用再纠结接口需要拆得多细才合理。接口由使用方按需定义,而不用事前规划,也不需要绘制类库的继承树图。
√ 在golang中,不用为了实现一个接口而导入一个包,因为多引用一个外部的包,就意味着更多的耦合。接口由使用方按自身需求来定义,使用方无需关心是否有其他模块定义过类似的接口。
• 接口赋值
▶ 对象实例赋值给接口
▪ 语法如下
// 接口定义 type IMyInterface1 interface { Func1(参数列表) (返回值列表) Func2(参数列表) (返回值列表) Func3(参数列表) (返回值列表) } type IMyInterface2 interface { Func1(参数列表) (返回值列表) } // 类定义 type MyClass struct { // 成员变量 } func (p *MyClass) Func1(参数列表) (返回值列表) { // 函数体 } func (p *MyClass) Func2(参数列表) (返回值列表) { // 函数体 } func (p *MyClass) Func3(参数列表) (返回值列表) { // 函数体 } func (p *MyClass) Func4(参数列表) (返回值列表) { // 函数体 } // 接口赋值 var myInterface1 IMyInterface1 = new(MyClass) var myInterface2 IMyInterface2 = new(MyClass) // 接口调用 myInterface1.Func1(参数列表) myInterface1.Func2(参数列表) myInterface1.Func3(参数列表) myInterface2.Func1(参数列表)
▪ 示例如下
package main import "fmt" type IMyInterface1 interface { Func1() bool Func2() bool Func3() bool } type IMyInterface2 interface { Func1() bool } type MyClass struct { } func (p *MyClass) Func1() bool { fmt.Println("MyClass.Func1()") return true } func (p *MyClass) Func2() bool { fmt.Println("MyClass.Func2()") return true } func (p *MyClass) Func3() bool { fmt.Println("MyClass.Func3()") return true } func (p *MyClass) Func4() bool { fmt.Println("MyClass.Func4()") return true } func main() { var myInterface1 IMyInterface1 = new(MyClass) var myInterface2 IMyInterface2 = new(MyClass) myInterface1.Func1() // MyClass.Func1() myInterface1.Func2() // MyClass.Func2() myInterface1.Func3() // MyClass.Func3() myInterface2.Func1() // MyClass.Func1() }
▶ 接口之间赋值
√ 在golang中,只要两个接口拥有相同的方法列表,那么它们就是等同的,可以相互赋值。
√ 在golang中,等同的接口可以分布在不同的包中,包并不是判断接口是否等同的条件之一。
√ 在golang中,接口赋值并不要求两个接口必须等价。如果接口A的方法列表是接口B的方法列表的子集,那么接口B是可以赋值给接口A,但是反过来不成立。
▪ 语法如下
// 接口定义 type IMyInterface1 interface { Func1(参数列表) (返回值列表) Func2(参数列表) (返回值列表) } type IMyInterface2 interface { Func1(参数列表) (返回值列表) Func2(参数列表) (返回值列表) } // 类定义 type MyClass struct { // 成员变量 } func (p *MyClass) Func1(参数列表) (返回值列表) { // 函数体 } func (p *MyClass) Func2(参数列表) (返回值列表) { // 函数体 } func (p *MyClass) Func3(参数列表) (返回值列表) { // 函数体 } // 接口赋值 var myInterface1 IMyInterface1 = new(MyClass) var myInterface2 IMyInterface2 = myInterface1 // 接口调用 myInterface1.Func1(参数列表) myInterface1.Func2(参数列表) myInterface2.Func1(参数列表) myInterface2.Func2(参数列表)
▪ 示例如下
package main import "fmt" type IMyInterface1 interface { Func1() bool Func2() bool } type IMyInterface2 interface { Func1() bool Func2() bool }
type IMyInterface3 interface {
Func1() bool
}
type MyClass struct { } func (p *MyClass) Func1() bool { fmt.Println("MyClass.Func1()") return true } func (p *MyClass) Func2() bool { fmt.Println("MyClass.Func2()") return true } func (p *MyClass) Func3() bool { fmt.Println("MyClass.Func3()") return true } func main() { var myInterface1 IMyInterface1 = new(MyClass) var myInterface2 IMyInterface2 = myInterface1 // 等同接口
var myInterface3 IMyInterface3 = myInterface2 // 子集接口 myInterface1.Func1() // MyClass.Func1() myInterface1.Func2() // MyClass.Func2() myInterface2.Func1() // MyClass.Func1() myInterface2.Func2() // MyClass.Func2()
myInterface3.Func1() // MyClass.Func1() }
• 接口查询
▶ 接口查询
√ 接口查询功能一:检查接口A指向的对象实例O是否实现了接口B,如果是则执行特定的代码。
√ 接口查询功能二:检查接口A指向的对象实例O是否是类型T,如果是则执行特定的代码。
√ 接口查询是运行期行为,编译器编译期不能确定。
▪ 语法如下
// 接口定义 type IMyInterface1 interface { // 函数列表 } type IMyInterface2 interface { // 函数列表 } // 类定义 type MyClass struct { // 成员变量 } // 接口赋值 var myInterface1 IMyInterface1 = new(MyClass) // 查询myInterface1指向的对象实例是否实现了IMyInterface2类型接口,如果实现,返回的myInterface2是指向对象的IMyInterface2类型接口实例 if myInterface2, ok := myInterface1.(IMyInterface2); ok { // 查询成功,myInterface2为IMyInterface2类型接口实例 } // 查询myInterface1指向的对象是否是MyClass类型实例,如果是,返回的myInstance是指向该对象的实例 if myInstance, ok := myInterface1.(*MyClass); ok{ // 查询成功,myInstance为myInterface1指向的对象实例 }
▪ 示例如下
package main import ( "fmt" ) type IMyInterface1 interface { Func1() bool Func2() bool } type IMyInterface2 interface { Func1() bool } type MyClass struct { Width, Height float64 } func (p *MyClass) Func1() bool { fmt.Println("MyClass.Func1() - Width =", p.Width) return true } func (p *MyClass) Func2() bool { fmt.Println("MyClass.Func2() - Height =", p.Height) return true } func main() { var myInterface1 IMyInterface1 = &MyClass{100, 200} if myInterface2, ok := myInterface1.(IMyInterface2); ok { myInterface2.Func1() // MyClass.Func1() - Width = 100 } if myInstance, ok := myInterface1.(*MyClass); ok { myInstance.Func1() // MyClass.Func1() - Width = 100 myInstance.Func2() // MyClass.Func2() - Height = 200 fmt.Println(myInstance.Width) // 100 fmt.Println(myInstance.Height) // 200 } }
▶ 类型查询
▪ 空接口:Any类型
√ golang中空接口interface{}可以指向任何对象实例。
√ golang中空接口interface{}看起来像是可以指向任何对象的Any类型,类似于COM中的IUnknown。
package main import ( "fmt" "reflect" ) func main() { var v1 interface{} = 1 var v2 interface{} = "abc" var v3 interface{} = &v2 var v4 interface{} = struct{ X int }{1} var v5 interface{} = &struct{ X int }{1} fmt.Println(reflect.TypeOf(v1)) // int fmt.Println(reflect.TypeOf(v2)) // string fmt.Println(reflect.TypeOf(v3)) // *interface {} fmt.Println(reflect.TypeOf(v4)) // struct { X int } fmt.Println(reflect.TypeOf(v5)) // *struct { X int } }
▪ 类型查询语法
√ golang中使用type switch语句可以查询接口指向对象的真实数据类型。
var v interface{} = obj switch instance := v.(type) { // 返回的instance是指向接口v指向的对象 case int: ... case string: ... ... default: }
▪ 类型查询示例
package main import ( "fmt" ) type Stringer interface { String() string } type MyString struct { content string } func (str *MyString) String() string { return str.content } func PrintString(args ...interface{}) { for _, arg := range args { switch instance := arg.(type) { case string: fmt.Println(instance) default: if v, ok := arg.(Stringer); ok { // 如果对象具有Stringer接口 fmt.Println(v.String()) } else { // 不能打印的非字符串类型 fmt.Println("unexpected string type") } } } } func main() { PrintString("abc") // abc PrintString(&MyString{"hello"}) // hello PrintString(123) // unexpected string type }
• 接口组合
√ 类似struct的匿名组合,golang也为接口提供了继承机制,即接口也可以匿名组合,只不过接口只包含方法,而不包含任何成员变量。
√ 接口组合与其他高级编程语言中的表现一致,即接口A可以被另一个接口B继承,形成接口继承体系。
▶ 基本语法
type MyInterface1 interface { // 函数列表 } type MyInterface2 interface { MyInterface1 // 函数列表 }
▶ 继承规则
√ 匿名组合的接口不可拥有同名函数,否则将给出错误:duplicate method XXXX
▪ 示例如下
package main import ( "fmt" ) type MyInterface1 interface { Func1() bool } type MyInterface2 interface { //Func1() bool // duplicate method Func1 Func2() bool } type MyInterface3 interface { MyInterface1 MyInterface2 //Func1() bool // duplicate method Func1 Func3() bool } type MyClass struct { } func (p *MyClass) Func1() bool { fmt.Println("MyClass.Func1()") return true } func (p *MyClass) Func2() bool { fmt.Println("MyClass.Func2()") return true } func (p *MyClass) Func3() bool { fmt.Println("MyClass.Func3()") return true } func main() { var myInterface3 MyInterface3 = new(MyClass) myInterface3.Func1() // MyClass.Func1() myInterface3.Func2() // MyClass.Func2() myInterface3.Func3() // MyClass.Func3() }