Code reuse in Go language-inheritance or composition?

Code reuse in Go language-inheritance or composition?

The story starts with a trap I encountered when I wanted to pretend to be a little professional in a project.

Code reuse

In this project, we already have code similar to the following:

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.

Then the new demand I received is like this, I need to develop a new user, which has some of the same behaviors as the current user. Of course, the main thing is that there are many different behaviors. As an old driver, I certainly know that these different places are what I need to focus on and realize. In order to distinguish between these two types of users, let's call them ordinary users and literary users. Because we already have the implementation code of ordinary users, as a senior (mis) Java engineer, I want to implement code reuse by inheriting this ordinary user. However, the sadness is so great, I found that inheritance is not supported in the Go language.

Embedded type

Well, as long as the mind does not slip, there are more solutions than difficulties. I found that there is something called Embedding in Go. In some articles on the Internet, they said that this is how inheritance is implemented in Go. But it seems that this is more like a combination in Java, at least syntactically, right?

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.

Nice job! In this way, I can reuse User’s sayName method, as long as the sayType method is implemented with my own logic. This is exactly what I want.

inherit? combination?

However, young man, please stay! Let's try the sayHi method to see what happens?

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.

This unscientific! In Java, subclasses will always call their own methods (methods of the parent class have been overridden). Unless the subclass does not override the method of the parent class, the method inherited from the parent class is used. In this example, I override (in fact, there is no such concept in Go) sayType method, but when we call it in sayHi, this override method is not called, but the original method of the parent class is used.

In fact, type embedding is not inheritance. It is just some form of syntactic sugar. In object-oriented programming, subclasses should be used as parent classes. In the Richter substitution principle, the subclass should be able to replace the parent class wherever needed. (Note that our first attempt to override the non-abstract method of the parent class has violated the Richter substitution principle). But in the above example, ArtisticUser and User are two different types. And cannot be used interchangeably.

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

If you try to uncomment the line, you will get a build error:

cannot use artisticUser (type *ArtisticUser) as type *User in argument to acceptUser

Let me say that embedded types are neither inheritance nor composition, but they are a bit like them.

Polymorphism

So back to my question. In fact, I shouldn't try to inherit in the first place. Even if Go provides an inheritance mechanism, overriding a non-abstract method of a parent class will break the Richter substitution principle. I wanted to try inheritance at first, but it was actually a lazy behavior, because I didn't want to refactor the existing code.

But we should not be afraid of refactoring. You see, even if I tried to avoid refactoring, I still fell into another ditch.

If I can do it again, I will choose Li Bai. . . Bah, if I can refactor the existing code, maybe I can try the interface. In the Go language, the interface is very flexible and is a means to achieve polymorphism.

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.

So I reused the sayName and sayHi methods, and left the sayType method to polymorphism.

perfect.

Reprinted at: https://my.oschina.net/u/1029640/blog/3053063


Reference : https://blog.csdn.net/weixin_34320159/article/details/92076650