Published on: February 7, 2021

Composition over inheritance. Java VS Go

Recently, I had the opportunity to delve into the Go programming language. It proved to be a fascinating experience, particularly in contrast to my extensive background in Java. I was struck by how years of immersion in the Java ecosystem can sometimes lead to the acceptance of what are, in essence, glaring anti-patterns disguised as common practices. One such anti-pattern is the pervasive use of class inheritance over composition. This particular issue is well-documented in Design Patterns: Elements of Reusable Object-Oriented”. a book published in 1994, long before the advent of Java and Go. Despite the passage of 27 years and the emergence of countless books and frameworks espousing modern software design principles, this anti-pattern persists, entrenched in both corporate codebases and popular libraries. “`java public class Person { protected String firstName; protected String lastName;

public String sayHello() {
    return this.firstName + this.lastName;
}

}

public class Emlpoyee extends Person { private String company;

//constructors
@Override
public String sayHello() {
    return super.sayHello() + " works in " + this.company;
}

} “`
Consider a typical example in Java, where class inheritance and method overriding are employed to model relationships between entities. For instance, it’s common to create an Employee class inheriting from a Person class, with the former overriding methods to include specific employment details. While this may seem innocuous at first, in practice, it often leads to unwieldy hierarchies and convoluted codebases, commonly referred to as “inheritance hell.”. Java, of course, provides alternatives to inheritance, such as composition, yet the language itself subtly encourages inheritance through mechanisms like the implicit extension of the Object class. Even prominent frameworks like JUnit and Spring (particularly in older versions) have historically favored inheritance-based designs. For example in JUnit 3 version, every test class should extend TestCase class. In older version of Spring MVC (version 2) in order to create controller you need to extend AbstractController. However, as the industry evolves, more elegant solutions have emerged. Modern frameworks, like Spring and JUnit, increasingly rely on annotations and annotation processors, moving away from inheritance-centric architectures. Despite this progress, legacy codebases remain rife with inheritance-based designs, perpetuating the anti-pattern.

Go aproach to inheritance

In contrast, the Go programming language adopts a pragmatic stance on inheritance by eschewing it entirely. Instead, Go emphasizes composition over inheritance through its struct-based approach. While it’s possible to implement similar designs in Java, the absence of inheritance in Go forces developers to adopt more modular and maintainable code practices from the outset. Here is the same example but in Go. “`go

type Name struct { FirstName string
LastName string
}

type Employee struct { Name Name Company string
} func (r *Employee) SayHello() string { return r.Name.sayHello() + r.LastName }

”` You could do similar things in Java or other object-oriented languages. The difference is, Java lets you easily fall into bad habits like relying too much on class inheritance, leading to messy code. Go, on the other hand, forces you to use composition instead, which keeps things cleaner from the start. This helps stop new programmers from repeating the same mistakes. It also stops people from making money by endlessly talking about old design patterns, showing that those patterns often come from flaws in language design.