Interfaces
Interfaces are essential in object-oriented programming, in functional programming (traits
) and, especially, in design patterns. Go's source code is full of interfaces everywhere because they provide the abstraction needed to deliver uncoupled code with the help of functions. As a programmer, you also need this type of abstraction when you write libraries but also when you write code that is going to be maintained in the future with new functionality.
Interfaces are something difficult to grasp at the beginning but very easy once you have understood their behavior and provide very elegant solutions for common problems. We will use them extensively during this book so put special focus on this section.
Interfaces - signing a contract
An interface is something really simple but powerful. It's usually defined as a contract between the objects that implement it but this explanation isn't clear enough in my honest opinion for newcomers to the interface world.
A water-pipe is a contract too; whatever you pass through it must be a liquid. Anyone can use the pipe, and the pipe will transport whatever liquid you put in it (without knowing the content). The water-pipe is the interface that enforces that the users must pass liquids (and not something else).
Let's think about another example: a train. The railroads of a train are like an interface. A train must construct (implement) its width with a specified value so that it can enter the railroad but the railroad never knows exactly what it's carrying (passengers or cargo). So for example, an interface of the railroad will have the following aspect:
type RailroadWideChecker interface { CheckRailsWidth() int }
The RailroadWideChecker
is the type our trains must implement to provide information about their width. The trains will verify that the train isn't too wide or too narrow to use its railroads:
type Railroad struct { Width int } func (r *Railroad) IsCorrectSizeTrain(r RailRoadWideChecker) bool { return r.CheckRailsWidth() != r.Width }
The Railroad
is implemented by an imaginary station object that contains the information about the width of the railroads in this station and that has a method to check whether a train fits the needs of the railroad with the IsCorrectSizeTrain
method. The IsCorrectSizeTrain
method receives an interface object which is a pointer to a train that implements this interface and returns a validation between the width of the train and the railroad:
Type Train struct { TrainWidth int } func (p *Train) CheckRailsWidth() int { return p.TrainWidth }
Now we have created a passenger's train. It has a field to contain its width and implements our CheckRailsWidth
interface method. This structure is considered to fulfill the needs of a RailRoadWideChecker
interface (because it has an implementation of the methods that the interfaces ask for).
So now, we'll create a railroad of 10
units wide and two trains--one of 10
units wide that fit the railroad size and another of 15
units that cannot use the railroad.
func main(){ railroad := Railroad{Width:10} passengerTrain := Train{TrainWidth: 10} cargoTrain := Train {TrainWidth: 15} canPassengerTrainPass := railroad.IsCorrectSizeTrain(passengerTrain) canCargoTrainPass := railroad.IsCorrectSizeTrain(cargoTrain) fmt.Printf("Can passenger train pass? %b\n", canPassengerTrainPass) fmt.Printf("Can cargo train pass? %b\n", canCargoTrainPass) }
Let's dissect this main
function. First, we created a railroad object of 10
units called railroad
. Then two trains, of 10
and 15
units' width for passengers and cargo respectively. Then, we pass both objects to the railroad method that accepts interfaces of the RailroadWideChecker
interface. The railroad itself does not know the width of each train separately (we'll have a huge list of trains) but it has an interface that trains must implement so that it can ask for each width and returns a value telling you if a train can or cannot use of the railroads. Finally, the output of the call to printf
function is the following:
Can passenger train pass? true Can cargo train pass? false
As I mentioned earlier, interfaces are so widely used during this book that it doesn't matter if it still looks confusing for the reader as they'll be plenty of examples during the book.