Imagine you were using Java, and you wanted to open a file and get something that implements some Reader
interface. You would probably have something like:
public interface Reader {
public int Read(byte[] b)
}
public interface Writer {
public int Write(byte[] b)
}
public class File implements Reader, Writer {
// stuff here
public byte[] Read() {
// read from the file and return a byte array with the data
}
public int Write(byte[] b) {
// do stuff to write to the file with the given byte array and return how many bytes were written
return b.length()
}
}
PS - I haven’t written Java in forever so I’m sure my syntax is wrong
Now when you go to open a file it is clear that this File
type implements both the Reader and Writer interfaces.
In Go those interfaces are implemented implicitly. That is, we don’t have to say implements X, Y, Z
- if it has the correct methods it automatically implements those interfaces. That means that because os.File
has the Read method, it implements the io.Reader
interface, and similarly because it has the Write method, it implements the io.Writer
interface.
This means we can do somethign like this:
var r io.Reader = &os.File{}
That is, an os.File
will implement the io.Reader
interface so we can assign it to a variable that is of that type. As a result we can also do this:
png, err := os.Create("something.png")
if err != nil {
log.Fatal(err)
}
jpg, err := os.Open("something.jpg")
if err != nil {
log.Fatal(err)
}
convertJPEGToPNG(png, jpg)
And because jpg
is a file, it implements io.Reader
, and because png
is an os.File
as well it implements io.Writer
.
Finally, all of this probably has you thinking, “Why not just write a function that takes in two files?”
The reason we don’t do this is because we only need the Read
and Write
methods to make our code work, and by using interfaces we can now also use this function to do things like read a jpg
from the hard drive adn write the results to an http.ResponseWriter
, writing the result directly to a web request response instead of having to first write it to the hard drive, then copy it back into the web request response.
If convertJPEGToPNG
only took in files we would have to open the JPG, create a PNG, convert the JPG to PNG, return the PNG to the user who made the web request, then finally delete the PNG from our server so we don’t run out of hard drive space. With the current implementation we can simply do:
func httpHandler(w http.ResponseWriter, r *http.Request) {
jpg := // get image from r, the http.Request, assuming the use uploaded it
convertJPEGToPNG(w, jpg)
}
And that code would convert a JPEG into a PNG and return the PNG to the user without ever writing that PNG to our hard drive. Neat, right?
There are other reasons as well (like testing), but I’ll leave it at that.
Hope this helps!