-
Notifications
You must be signed in to change notification settings - Fork 18.3k
Description
I would like to propose the removal of embedded struct in Go 2.x, because it does not solve inheritance problems, and the alternatives may actually be better.
First, let us examine the pros and cons of inheritance (more specifically, implementation inheritance). Inheritance groups common functionalities into the base class. It allows us to make a change in one place (instead of being scattered everywhere), and it will be automatically propagated to the derived classes. However, it also means the derived classes will be highly coupled to the base classes. The base class implementor, who does not need to know anything about the derived classes, may inadvertently break the derived classes when making changes to the base class. In addition, inheritance encourages a type hierarchy. In order to understand a derived class, one need to know its base classes (which includes its immediate parents, its grandparents, its great grandparents, etc.). It gives us the classic OOP problem where in order to get a banana, you need the monkey and the whole jungle. Modern OOP proponents now encourage "composition over inheritance".
Go's embedded struct is unique. At a glance, it appears to be a composition. However, it behaves exactly like the classic inheritance, including the pros and cons of inheritance. The base struct implementor may inadvertently break the derived structs. In order to understand a derived struct, one must look up the definition of its base struct, creating a hierarchy of types. Thus, Go's embedded struct is actually inheritance (although it is a crippled one).
I say it is a crippled inheritance because Go's embedded struct does not provide the same flexibility as the actual inheritance. One such example is this:
driver.Drive(s60.Volvo.Car.Vehicle)
Even though s60 is a vehicle, in Go, you need to get to the actual base class in order to use it as an argument.
If the Go's Team considers keeping this inheritance property, I say they should either go all the way with inheritance, or scrap it altogether. My proposal is to scrap it.
Here are my reasons for scrapping it:
- It offers no benefits. Although it looks like a composition, it is not. It behaves like inheritance with all the flaws of inheritance. It offers less flexibility than the real inheritance for no apparent benefits.
- It does not take much effort to write forwarding methods. In fact, it may be better to do an actual composition without the embedded struct feature. The slight extra effort pays off in the long run.
- If you truly do not want to write forwarding methods, you may refactor common methods into functions that accept a common interface. You can achieve the same effect as inheritance without having to couple the types. For instance, instead of
type BaseDriver struct{}
func (b BaseDriver) Drive(vehicle Vehicle){}
type TructDriver struct{
BaseDriver
}
type TaxiDriver struct{
BaseDriver
}
You can do this
func Drive(driver Driver, vehicle Vehicle) {}
Let me know what you think.
Thanks.
Henry