This blog is about embedding in Go language (which many, including me, refer to as “Golang,” because of its domain name, golang.org) and the various ways to do it. But before embedding, it is important to understand some Object-Oriented Programming (OOP) concepts and establish some contexts that may surface a need for embedding. We will follow this with a detailed discussion on embedding and end with a problem that will require the use of all the concepts discussed. I’ve even included some trivia along the way (answers to all of the trivia questions appear at the end of this post).
Contexts and concepts
Inheritance defines an
is-a relationship between two entities, where one entity is the base and the other is derived. A derived entity inherits properties and behavior from the base entity. It may be said that “derived is a base,” or “derived is a type of base,” as below:
An association defines a
has-a(aggregation) or a
owns-a(composition) relationship between two entities, where one entity contains the other as its member field:
In Aggregation, the lifecycles of both entities are independent and one can exist even without the other:
In Composition, the entities’ lifecycles are coupled and the contained entity cannot live independently.
Inheritance vs. association
|Defined At||compiled time||runtime|
|Ease of testing||hard||easy with mocking|
|Forwarding methods||not required||required|
|Base class type||abstract (pure virtual) or concrete||concrete only|
As Inheritance is “white-box” reuse, it breaks encapsulation by exposing the base class’ private entities to the derived class. Use Inheritance where there is a strict has-a relationship. Association would lead to forwarding all methods.
Inheritance, when used incorrectly, can lead to less flexible and highly coupled designs. It is recommended to use association over the inheritance.
In “Design Patterns: Elements of Reusable Object-Oriented Software,” the authors (Erich Gamma, John Vlissides, Richard Helm, and Ralph Johnson, a.k.a. the “Gang of Four”) say you should favor object composition over class inheritance. There is a lot of debate and conflict on whether Golang is truly an object-oriented language, because it does not support inheritance, although polymorphism can be achieved through interfaces. This has been answered in Golang FAQs.
According to Rob Pike, one of the language’s major contributors, Golang is “a profoundly object-oriented language.” (See https://www.youtube.com/watch?v=rKnDgT73v8s&t=750s) Russ Cox, lead developer for Go at Google, the language is “object oriented, but it’s not type oriented.” (See https://www.youtube.com/watch?v=yx7lmuwUNv8&t=931s)
Golang does not support inheritance but supports association via embedding. This can be done in three ways:
- Interfaces in interfaces
- Interfaces in structs
- Structs in structs
Interfaces in interfaces
- If an interface is embedded inside another interface, the embedding interface becomes the union of its methods and the methods of all the embedded interfaces.
- Interface embedding is shorthand for redeclaring all the methods of the embedded interfaces.
- Before Go 1.14, embedding two interfaces with the same methods would throw an error. This is allowed in newer versions.
Trivia Question #1: What if both the embedded interfaces have a method(s) with the same name but with different signature(s)?
Interfaces in structs
- Embed an interface in place of a struct if the struct has many forms and all the forms adhere to an interface.
- The concrete struct can be assigned to the interface when creating the embedding struct.
- All the methods of the interface are promoted to the embedding struct.
- The embedding struct has the option to override the implementation of the embedded struct.
- By default, the interface will be nil until assigned and any call to forwarded methods will panic at runtime.
Trivia Question #2: What if both the embedded interfaces have a method(s) with the same name and same signature(s)?
Structs in structs
- A struct can be embedded in another struct directly without a variable name. This will lead to a promotion of all the fields and methods of the embedded struct, which can be called directly by the embedding struct.
- Non-exported fields or methods will not be promoted across packages.
- The forwarding will not happen if there is a conflict in the field or method name of the embedding and embedded struct.
- The conflicting field and method will have to be explicitly called using the embedded struct name.
What if we do not want to expose all the properties or methods of the base struct?
- A struct can be declared as a field in another struct using any variable name. This will not promote any fields or methods of the enclosed struct.
- The methods will have to be forwarded by the enclosing struct or the caller will have to use the field variable to access the composed struct.
- Use this over direct embedding if only selective features of the enclosed struct are to be exposed.
Trivia Question #3: What if both the embedded structs have a method(s) with the same name and same signature(s)?
Trivia question #4: (A combination of trivia questions #2 and #3.) Can you have duplicate methods in embedded interfaces? Duplicate methods in embedded structs? Can you call the duplicate method on the embedding struct that implements the embedding interface?
Putting this into use: how to call derived object functions from base object functions
Consider an interface with methods Start() and Process(). Assume Start() must call Process(). Let Foo and Bar be two structs that implement the above interface. Now if both the derived structs have the same code for Start(), how do we avoid duplicating code for the Start()?
The obvious solution is to introduce a base struct that implements the Start() and both Foo and Bar embed the base struct. Now how do we make the base object’s Start() call the corresponding Process() of derived objects Foo and Bar?
This idea is to create a cycle between the base struct and the derived structs Foo and Bar.
- Assign the derived structs base interface to itself and return the derived struct. Foo: Returns its own instance by assigning itself to the base interface.
- Assign the base structs interface to the derived struct and return the base struct. Bar: Returns base instance by embedding itself.
Though Golang does not directly support inheritance, it can be “mimicked” in some sense using embedding. Embedding is the bridge between Golang and OOP, making it a very powerful and important concept to understand. Hope you had some takeaway from this blog. Keep a watch on the VMware OCTO blog for future posts.
- Error: duplicate method Close
- Error: ambiguous selector ps.Close
- Error: ambiguous selector rw.Close
- Error: PrinterScanner.Close is ambiguous/cannot use ps (type PrinterScanner) as type ReaderWriter in argument to Close: PrinterScanner does not implement ReaderWriter (missing close method)
|Interfaces in interfaces||https://play.golang.org/p/4cJtnI5q53m|
|Interfaces in structs||https://play.golang.org/p/uvcettmvhed|
|Structs in structs by embedding||https://play.golang.org/p/D2w4KxpiI3C|
|Structs in structs as fields||https://play.golang.org/p/hXMF_9ZKhi9|
|Putting it to use||https://play.golang.org/p/1xU0BemAYsV|