The Flexible Go, the Traditional Java, the Progressive Python, and the Dynamic Node.js: An Analogy for Understanding Interfaces in Different Languages
Disclaimer: Please note that this post is not intended to suggest that other object-oriented languages are inferior to Go. Each programming language has its own strengths and weaknesses, and there are many factors to consider when choosing a language for a particular task. The purpose of this post is simply to compare the interface implementation features of Go to those of other object-oriented languages and discuss how they can be used in the context of hiring service providers.
Interfaces are a powerful feature of many programming languages that allow you to specify the behaviors that a type must implement. In Go, interfaces are satisfied implicitly, meaning that a type doesn’t need to explicitly declare that it implements an interface as long as it has the required methods. This provides a lot of flexibility as you will see . In this post, we’ll compare Go’s interfaces to those of other object-oriented languages and see how they stack up through an anaology of hiring the best service providers.
Go is the more flexible “Employer”
Go is like a flexible and open-minded employer that is willing to hire any qualified candidate, regardless of their background or experience. It doesn’t matter if a candidate explicitly states that they have the required skills or not — as long as they can perform the job well, Go is happy to hire them.
Imagine that a company wants to hire service providers to perform various tasks. In Go, the company can specify the tasks that the service providers must be able to perform by defining an interface. The service providers can then implement the interface by providing implementations for the required methods. The company can then hire any service provider that satisfies the interface, regardless of whether the service provider explicitly declares that it implements the interface.
For example, the company might define the following interface for service providers that can paint buildings:
type Painter interface {
Paint(color string)
}
Now, any service provider that has a method named Paint
with the correct signature can implement the Painter
interface. For example, the following service provider implements the Painter
interface:
type PaintingCompany struct {
// fields
}
func (c *PaintingCompany) Paint(color string) {
// implementation
}
Imagine that PaintingCompany
is an actual popular painting company that the company wants to hire. The company can hire the PaintingCompany
by creating a variable of the interface type Painter
and assigning a value of type PaintingCompany
to it:
pc := PaintingCompany{}
var p Painter = &pc
Now, the company can use the p
variable to call the Paint
method on the PaintingCompany
service provider like this:
p.Paint("red")
Imagine that the company also wants to hire another service provider, DecoratingCompany
, which can decorate buildings with wallpaper. The DecoratingCompany
can implement the Painter
interface by providing an implementation for the Paint
method like this:
type DecoratingCompany struct {
// fields
}
func (c *DecoratingCompany) Paint(color string) {
// implementation
}
Now, the company can hire the DecoratingCompany
by creating a variable of the interface type Painter
and assigning a value of type DecoratingCompany
to it:
dc := DecoratingCompany{}
var d Painter = &dc
The company can now use the d
variable to call the Paint
method on the DecoratingCompany
service provider like this:
d.Paint("blue")
In this way, the company can hire any service provider that satisfies the Painter
interface, regardless of whether the service provider explicitly declares that it implements the interface.
Java, the traditional and structured employer
Java is like a traditional and structured employer that values experience and qualifications. It only hires candidates who have explicitly declared that they have the skills and credentials needed for the job.
In Java, the company would need to explicitly declare that a service provider class implements the Painter
interface. For example, the PaintingCompany
class would need to declare that it implements the Painter
interface like this:
public class PaintingCompany implements Painter {
// fields and methods
}
Here, the PaintingCompany
class has a paint
method with the correct signature to satisfy the Painter
interface. However, it must explicitly declare that it implements the Painter
interface using the implements
keyword.
In this way, the company can only hire service providers that explicitly declare that they implement the Painter
interface. For example, the company could hire the PaintingCompany
in Java like this:
PaintingCompany pc = new PaintingCompany();
Painter p = pc;
p.paint("red");
Here, the PaintingCompany
class is being assigned to a variable of the Painter
interface type. Since the PaintingCompany
class has declared that it implements the Painter
interface, this assignment is valid. The company can then use the p
variable to call the paint
method on the PaintingCompany
Python, the modern and progressive employer
Python is like a modern and progressive employer that values diversity and inclusivity. It is open to hiring candidates from a wide range of backgrounds and experiences, as long as they can demonstrate their ability to do the job.
In Python the company would need to specify that the PaintingCompany
class implements the Painter
interface like this:
from abc import ABC, abstractmethod
class Painter(ABC):
@abstractmethod
def paint(self, color: str) -> None:
pass
Here, the Painter
interface is defined using the ABC
(abstract base class) and abstractmethod
decorators from the abc module. The paint
method is defined as an abstract method using the @abstractmethod
decorator, which means that any class that inherits from the Painter
interface must implement the paint
method.
Now, imagine that the company wants to hire a service provider named PaintingCompany
that can paint buildings. The PaintingCompany
can implement the Painter
interface by providing an implementation for the paint
method like this:
class PaintingCompany(Painter):
def paint(self, color: str) -> None:
# implementation
Here, the PaintingCompany
class has a paint
method with the correct signature to satisfy the Painter
interface. It also specifies that it inherits from the Painter
interface using the parentheses after the class name.
Now, the company can hire the PaintingCompany
by creating a variable of the interface type Painter
and assigning a value of type PaintingCompany
to it:
pc = PaintingCompany()
p: Painter = pc
Here, the variable p
is of type Painter
, and its value is an instance of the PaintingCompany
class. Since the PaintingCompany
class has specified that it implements the Painter
interface, this assignment is valid. The company can then use the p
variable to call the paint
method on the PaintingCompany
object like this:
p.paint("red")
This code will call the paint
method on the PaintingCompany
object, passing in the string "red" as the argument.
In the case when the company also wants to hire another service provider, DecoratingCompany
. The DecoratingCompany
can implement the Painter
interface by providing an implementation for the paint
method like this:
class DecoratingCompany(Painter):
def paint(self, color: str) -> None:
# implementation
Now, the company can hire the DecoratingCompany
by creating a variable of the interface type Painter
and assigning a value of type DecoratingCompany
to it:
dc = DecoratingCompany()
d: Painter = dc
the company can now use the d
variable to call the paint
method on the DecoratingCompany
service provider like this:
d.paint("blue")
In this way, the company can hire any service provider that satisfies the Painter
interface in Python.
NodeJS, the dynamic and fast-paced employer
Node.js is like a dynamic and fast-paced employer that values innovation and adaptability. It is willing to hire candidates who are able to think on their feet and come up with creative solutions to problems.
For example in NodeJS. The company might define the Painter
interface like this:
class Painter {
constructor() {
if (new.target === Painter) {
throw new TypeError("Cannot construct Abstract instances directly");
}
}
paint(color) {
throw new TypeError("Do not call abstract method paint from child.");
}
}
Here, the Painter
interface is defined using a class with an abstract paint
method. The constructor function throws an error if an instance of the Painter
class is created directly, since the Painter
class is meant to be an abstract base class. The paint
method also throws an error if it is called directly from a child class, since it is meant to be implemented by child classes.
Now to hire a service provider named PaintingCompany
that can paint buildings, The PaintingCompany
can implement the Painter
interface by providing an implementation for the paint
method like this:
class PaintingCompany extends Painter {
constructor() {
super();
// fields
}
paint(color) {
// implementation
}
}
Here, the PaintingCompany
class has a paint
method with the correct signature to satisfy the Painter
interface. It also specifies that it extends the Painter
interface using the extends
keyword.
Now, the company can hire the PaintingCompany
by creating a variable of the interface type Painter
and assigning a value of type PaintingCompany
to it:
const pc = new PaintingCompany();
const p: Painter = pc;
Here, the variable p
is of type Painter
, and its value is an instance of the PaintingCompany
class. Since the PaintingCompany
class has specified that it extends the Painter
interface, this assignment is valid.
The company can now use the p
variable to call the paint
method on the PaintingCompany
service provider like this:
p.paint("red");
This code will call the paint
method on the PaintingCompany
object, passing in the string "red" as the argument.
In the case when hiring another service provider, DecoratingCompany
, which can decorate buildings with wallpaper. The DecoratingCompany
can implement the Painter
interface by providing an implementation for the paint
method like this:
class DecoratingCompany extends Painter {
constructor() {
super();
// fields
}
paint(color) {
// implementation
}
}
Now, the company can hire the DecoratingCompany
by creating a variable of the interface type Painter
and assigning a value of type DecoratingCompany
to it:
const dc = new DecoratingCompany();
const d: Painter = dc;
The company can now use the d
variable to call the paint
method on the DecoratingCompany
service provider like this:
d.paint("blue");
In this way, the company can hire any service provider that satisfies the Painter
interface in NodeJS.
Just like in Python and Go, the company can hire any type that satisfies the Painter
interface, regardless of whether the type explicitly declares that it implements the interface. This is in contrast to many other object-oriented languages, which require types to explicitly declare that they implement an interface.
Conclusion
In this analogy, Go, Java, Python, and Node.js are compared to different types of employers that have different hiring practices. Go is like a flexible and open-minded employer, Java is like a traditional and structured employer, Python is like a modern and progressive employer, and Node.js is like a dynamic and fast-paced employer. These differences can help developers understand the unique characteristics and approaches of each language when it comes to implementing interfaces and hiring service providers. Which one is your favorite? Please leave your comments below.
I hope this analogy has helped shed some light on the concept of interfaces in Go, Java, Python, and Node.js. As a beginner, I am always learning and trying to improve my understanding of different concepts of different languages. If you noticed any mistakes in this post, please don’t hesitate to let me know so that we can learn from each other and grow together. If you enjoyed this post and would like to stay updated on my future content, please give me a “CLAP” and follow me here and on Twitter. Thank you for reading!