封装数据和行为
数据封装
结构定义
type Employee struct { Id string Name string Age int }
field后面没有逗号
实例创建及初始化
e := Employee{"0", "Bob", 20} e1 := Employee{Name: "Mike", Age: 30} e2 := new(Employee) // 注意这里返回的引用/指针,相当于e := &Employee{} e2.Id = "2" // 与其他主要编程语言的差异,通过实例的指针访问成员不需要使用-> e2.Age = 22 e2.Name = "Rose"
检验一下类型
t.Logf("e is %T", e) t.Logf("e2 is %T", e2)
=== RUN TestCreateEmployeeObj
--- PASS: TestCreateEmployeeObj (0.00s)
func_test.go:79: e is fun_test.Employee
func_test.go:80: e2 is *fun_test.Employee
PASS
行为(方法)定义
与其他主要编程语言的差异
两种方式定义行为:
// 第一种定义方式在实例对应方法被调用时,实例成员会进行值复制 func (e Employee) String() string { return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age) } // 通常情况下为了避免内存拷贝我们使用第二种定义方式 func (e *Employee) String() string { return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age) }
接口
定义交互协议
Java
如果把AClient和接口A打包在一起为packageA,Almpl打包为B
那么A和B 是相互依赖的
一般是接口独立在一个package中,解决循环依赖
Go
Duck Type式接口实现
没有显示表明实现接口,只是实现了接口的方法WriteHelloWorld()
type Programmer interface { WriteHelloWorld() string } type GoProgrammer struct { } func (g *GoProgrammer) WriteHelloWorld() string { return "fmt.println("Hello World")" } func TestClient(t *testing.T) { var p Programmer p = new(GoProgrammer) t.Log(p.WriteHelloWorld()) }
=== RUN TestClient
--- PASS: TestClient (0.00s)
func_test.go:117: fmt.println("Hello World")
PASS
与其他主要编程语言的差异
1.接口为非入侵性,实现不依赖于接口定义
2.所以接口的定义可以包含在接口使用者包内
接口变量
自定义类型
1.type IntConvertionFn func(n int) int
2.type MyPoint int
func timeSpent(inner func(op int)int) func(opt int) int { return func(n int) int { start := time.Now() ret := inner(n) fmt.Println("time spent:", time.Since(start).Seconds()) return ret } }
参数特别长,自定义一个类型
type IntConv func(op int) int func timeSpent(inner IntConv) func(opt int) int { return func(n int) int { start := time.Now() ret := inner(n) fmt.Println("time spent:", time.Since(start).Seconds()) return ret } }
扩展和复用
争议最大的一块
Go不支持继承
Java代码
class Pet { public void speak(){ System.out.print("..."); } public void speakTo(String name){ this.speak(); System.out.println(name); } } class Dog extends Pet{ @Override public void speak(){ System.out.println("Wang!"); } } public class InheritanceTest{ private void makePetSpeak(Pet p){ p.speak(); System.out.println(" Pet speak."); } @Test public void testSubClassAccess(){ Pet aDog = new Dog(); aDog.speak(); aDog.speakTo("Chao"); } @Test public void testLSP(){ Dog aDog = new Dog(); makePetSpeak(aDog); } }
makePetSpeak()的参数是Pet类型,接收子类Dog类型是可以的,且会调用Dog的speak
在Go中做同样的事情
使用内嵌
没有继承
dog只能再重写个不同的SpeakTo方法
type Pet struct { } func (p *Pet)Speak() { fmt.Print("...") } func (p *Pet)SpeakTo(host string) { p.Speak() fmt.Println(" ", host) } type Dog struct { p *Pet } func (d *Dog)Speak() { fmt.Println("Wang") } func (d *Dog)SpeakTo(host string) { d.Speak() //d.p.SpeakTo(host) fmt.Println(" ", host) } func TestDog(t *testing.T){ dog := new(Dog) dog.SpeakTo("chao") }
多态
想支持多态,要使用接口
空接口与断言
1.空接口可以表示任何类型
2.通过断言来将空接口转换为定制类型
v, ok := p.(int) // ok=true 时则为转换成功
func DoSomething(p interface{}) { if i, ok := p.(int); ok{ fmt.Println("Integer", i) return } if s, ok := p.(string); ok{ fmt.Println("string", s) return } fmt.Println("Unknow Type") } func TestEmptyInterfaceAssertion(t *testing.T) { DoSomething(10) DoSomething("10") }
=== RUN TestEmptyInterfaceAssertion
Integer 10
string 10
--- PASS: TestEmptyInterfaceAssertion (0.00s)
PASS
用switch可以简化上面代码
func DoSomething(p interface{}) { switch v := p.(type) { case int: fmt.Println("Integer", v) case string: fmt.Println("String", v) default: fmt.Println("Unknown Type") } } func TestEmptyInterfaceAssertion(t *testing.T) { DoSomething(10) DoSomething("10") }
Go接口最佳实践