故事要从我在一个项目中,想要伪装的专业一点而遇到的一个陷阱提及。编程
在这个项目中,咱们已经有了相似以下的代码:ide
package main import ( "fmt" ) func main() { user := &User{name: "Chris"} user.sayHi() } type User struct { name string } func (u *User) sayHi() { u.sayName() u.sayType() } func (u *User) sayName() { fmt.Printf("I am %s.", u.name) } func (u *User) sayType() { fmt.Println("I am a user.") }
I am Chris.I am a user.
而后我接到的新需求是这样的,我须要开发一种新的用户,它和当前这种用户有一些相同的行为。固然,最主要的是也有不少不一样的行为。做为一名老司机,我固然知道,这些不一样的地方才是我须要重点关注而且实现的。 为了区分这两种用户,咱们就叫他们普通用户和文艺用户吧。 由于咱们已经有了普通用户的实现代码了,做为一个资深(误)Java工程师,我想经过继承这个普通用户来实现代码的复用。然而悲伤辣么大,我发如今Go语言中是不支持继承的。ui
好吧,只要思想不滑坡,办法总比困难多。我发如今Go中有一种叫作Embedding的东西。在网上的一些文章中,他们说这就是Go中实现继承的方式。但是看起来,这更像是Java中的组合,至少语法上像,是不?code
package main import ( "fmt" ) func main() { artisticUser := &ArtisticUser{User: &User{name: "Chris"}} artisticUser.sayName() artisticUser.sayType() } type User struct { name string } func (u *User) sayHi() { u.sayName() u.sayType() } func (u *User) sayName() { fmt.Printf("I am %s.", u.name) } func (u *User) sayType() { fmt.Println("I am a user.") } type ArtisticUser struct { *User } func (u *ArtisticUser) sayType() { fmt.Println("I am an artistic user.") }
I am Chris.I am an artistic user.
干得漂亮!这样我就能够复用User的sayName方法,只要把sayType方法用我本身的逻辑实现就行了。这正是我想要的。orm
可是,少侠请留步!咱们试一下sayHi方法看看会发生什么?对象
package main import ( "fmt" ) func main() { artisticUser := &ArtisticUser{User: &User{name: "Chris"}} artisticUser.sayHi() } type User struct { name string } func (u *User) sayHi() { u.sayName() u.sayType() } func (u *User) sayName() { fmt.Printf("I am %s.", u.name) } func (u *User) sayType() { fmt.Println("I am a user.") } type ArtisticUser struct { *User } func (a *ArtisticUser) sayType() { fmt.Println("I am an artistic user.") }
I am Chris.I am a user.
这不科学!在Java里,子类老是会调用本身的方法的(已经override了父类的方法)。除非子类没有覆盖父类的方法,才会使用从父类继承来的方法。 在这个例子中,我override了(其实Go中没有这个概念)sayType方法,可是当咱们在sayHi中调用它时,却没有调用这个override方法,而是用了父类的原始方法。继承
实际上,类型嵌入不是继承。它只是某种形式上的语法糖而已。在面向对象编程中,子类应该是能够被当作父类来使用的。在里氏替换原则中,子类应该能在任何须要的地方替换掉父类。(注意一点,咱们这里一开始尝试覆盖父类的非抽象方法已经违背了里氏替换原则)。 可是在上边的例子中,ArtisticUser和User是两种不一样的类型。且不能替换使用。接口
package main import ( "fmt" ) func main() { user := &User{name: "Chris"} artisticUser := &ArtisticUser{User: user} fmt.Printf("user's type is: %T\n", user) fmt.Printf("artisticUser's type is: %T\n", artisticUser) acceptUser(user) //acceptUser(artisticUser) } type User struct { name string } func (u *User) sayHi() { u.sayName() u.sayType() } func (u *User) sayName() { fmt.Printf("I am %s.", u.name) } func (u *User) sayType() { fmt.Println("I am a user.") } type ArtisticUser struct { *User } func (a *ArtisticUser) sayType() { fmt.Println("I am an artistic user.") } func acceptUser(u *User) { }
user's type is: *main.User artisticUser's type is: *main.ArtisticUser
若是你尝试去掉注释掉的那一行,你会获得一个build错误:开发
cannot use artisticUser (type *ArtisticUser) as type *User in argument to acceptUser
要我说,嵌入类型既不是继承,也不是组合,只是跟它们都有点像。string
那么回到个人问题。事实上我一开始就不应尝试继承。即便Go提供了继承机制,覆盖一个父类的非抽象方法也将破坏里氏替换原则。我一开始想要试试继承实际上是一种偷懒的行为,由于我并不想重构已有的那么一大坨代码。
可是咱们不该该惧怕重构。你看,就算我想试着逃避重构,仍是掉进别的沟里了。
若是能重来,我要选李白。。。呸,若是能让我重构已有代码的话,也许我能够试试接口。在Go语言中,接口很是灵活,是实现多态的手段。
package main import ( "fmt" ) func main() { user := &User{name: "Chris"} user.ISubUser = &NormalUser{} user.sayHi() user.ISubUser = &ArtisticUser{} user.sayHi() } type ISubUser interface { sayType() } type User struct { name string ISubUser } func (u *User) sayHi() { u.sayName() u.sayType() } func (u *User) sayName() { fmt.Printf("I am %s.", u.name) } type NormalUser struct { } func (n *NormalUser) sayType() { fmt.Println("I am a normal user.") } type ArtisticUser struct { } func (a *ArtisticUser) sayType() { fmt.Println("I am an artistic user.") }
I am Chris.I am a normal user. I am Chris.I am a artistic user.
这样我就重用了sayName和sayHi方法,而且把sayType方法留给多态来实现。
完美。