Inheritance is an essential feature in many programming languages and trying to imagine the absence of it will bring up many thoughts, including code repetition.
It is interesting that the few times I have had a discussion with someone not comfortable with the lack of inheritance in Go, code repetition has been their issue. The issue is not the lack of inheritance, the issue is they are used to having inheritance. it is best to think the Go way to code in Go.
Java is one of the strongest cases for inheritance and yet James Gosling who is widely regarded as the father of Java, said this about inheritance. You can read full transcript here.
“Yes – without an inheritance hierarchy. Rather than subclassing, just use pure interfaces. It’s not so much that class inheritance is particularly bad. It just has problems.”
With that, you would reason with the Go creators for the choice to leave out inheritance. However, and they did not leave out inheritance without providing a way around it. In this post, I will provide a direct comparison between Java and Go. Showing how the features that inheritance brings to Java is still implemented in Go without it.
Subtyping
Typical scenario in Java is to have a class Person
and you want a class Student
that is derived from Person. Let us see how this is done in Java.
class Person {
String name;
int age;
public void print(){
System.out.printf("%s is %d years old", name, age);
}
}
class Student extends Person {
String studentId;
int yearOfStudy
public void print(){
System.out.printf("%s is a student with Student Id: %s", name, studentId)
}
}
In the example above, the class Student
is extending Person
and overriding the print()
method. If required, Java provides a way to access an overridden method of the base class using super.print()
. Now let us see how we can do the same in Go without inheritance.
type Person struct {
Name string
Age int
}
func (p *Person) Print()
fmt.Printf("%s is %d years old", p.Name, p.Age){
}
type Student struct {
Person
StudentId string
YearOfStudy int
}
func (s *Student) Print(){
fmt.Printf("%s is a student with Student Id: %s", s.Name, s.StudentId)
}
In the example above, you see the use of Type Embedding in Go. Go provides the ability to embed one type into another and through type promotion, have the identifiers of the inner type be accessible by values of the outer type. As you can see when we called s.Name
, Name
was declared by Person
type but we are able to access that field from a value of Student
type.
You may be wondering how you can access the inner type directly? We can still explicitly call Print()
from the inner type by using the name of the inner type. student.Person.Print()
.
student := Student{
Person{"John", 20},
"72636",
"2",
}
student.Print() //Outputs John is a student with Student Id: 72636
student.Person.Print() //Outputs John is 20 years old
Interfaces
Gophers claim this is one of the sweetest part of Go and I have no exception. Interfaces in Go are painless compared to Java. Let us look at the following Java code.
interface StringReader {
public int read(byte[] b);
}
interface BytesReader{
public int read(byte[] b);
}
class FileReader implements StringReader {
public int read(byte[] b){ ... }
}
In Java, you must use the implements
keyword to specify the interface is being implemented. FileReader
is not valid when the BytesReader
interface is required, even though it has the required method.
This is one thing Go has done right.
type StringReader interface {
Read(b []byte) (int, error)
}
type BytesReader interface {
Read(b []byte) (int, error)
}
type FileReader struct { ... }
func (f *FileReader) Read(b []byte) (int, error) { ... }
With Go, as long as the method declared by the interface has been implemented, values from these types are said to implement the interface. Pointers of type FileReader
implements both the StringReader
and ByteReader
interfaces. You do not need to use any keywords to define the relationship, it is just implemented.
So where is the feared code repetition?
Inheritance may be missing in Go but Go is not missing the features that inheritance brings to Java.