Interfaces are a mechanism for creating type contracts in software engineering. Depending on the language, they can be implemented as a set of methods, properties, or both.

Interfaces define a contract a type must adhere to. The interface contract can then be used as the type in code, rather than the original type. This contract allows for many different types to be used, essentially genericizing the code substantially.

Table of Contents

Multiple Inheritance

In most languages, you can only inherit from one class, but you can inherit from many interfaces. Multiple inheritance allows for more widely applicable code.

Explicit Interface Implementation

Explicit interface implementation is the more common form of interface implementation. Explicit implementation adherence is generally configured at the class level and allows for the compiler to directly check for implementation of the interface. Explicit implementation is something that requires direct access to the class if you want to map it to a different interface.

 1// Example of an interface in C#
 2public interface IStructure {
 3    // Return the structure's name
 4    string Name { get; }
 5}
 6
 7// Structure implements the IStructure interface
 8public class Structure : IStructure {
 9    public string Name { get; set; }
10}

Implicit Interface Implementation

While in most languages interfaces are defined and implemented by classes explicitly this is not always the case. For example, both Go and TypeScript use implicit interfaces, not pre-declared directly on a type, and are later inferred through a type contract negotiation process.

One of the great benefits of implicit interfaces in Go is the fact that a package can define an interface internally (without exporting it) and accept input from an external source and the compiler will automatically map it to the internal interface. This implicit relationship gives a user the ability to overriding data going into a library that accepts an interface where you may not be able to directly change the implementation of the library.

 1// Example of an implicit interface in Go
 2type Reader interface {
 3    Read(p []byte) (n int, err error)
 4}
 5
 6type myReader struct {}
 7
 8// Implement the Reader interface
 9func (r *myReader) Read(p []byte) (n int, err error) {
10    return 0, nil
11}

With Go, and Typescript for that matter, implicit interface implementation does not require a type assertion, minimizing boilerplate code. This is a form of type inference enables statically typed languages to automatically infer the type of a variable based on a type graph.

Example: Leveraging Implicit Interfaces in Go

Mocking data for unit testing can be difficult if your code is built to function directly on a struct type, but if you accept an interface your type implements, you can mock your data easily. This is due to the implicit nature of interfaces in Go.

A great example of mocking data using implicit interfaces is the http.Client which is a struct type. Generally, using the http.Client is done by calling the Do method which accepts an http.Request and returns an http.Response and an error. Since the Do method is a function, it can be inferred as an interface type implicitly in Go.

We can define a main file like this:

1package main  
2func main() {
3  API.Endpoint(http.DefaultClient, struct {
4    ID string `json:"id"`
5    // ...
6  })
7}

And define a secondary package like this:

 1package API
 2
 3// Define the accepted Client interface in the API package
 4type Client interface { // NOTE: This does NOT have to be exported to work
 5  Do(req *http.Request) (*http.Response, error)
 6}
 7
 8func Endpoint(client Client, myDTM *DTM) {
 9  req, _ := http.NewRequest("GET", "http://example.com", nil)
10  resp, _ := client.Do(req)
11  fmt.Println(resp.StatusCode)
12}

In the example above, we are using the http.DefaultClient as the client, but the API package doesn’t know about the http.Client type, it only knows that it is accepting a value implementing its internally defined Client interface.

So, to expand on this, if we wanted to test the Endpoint function, we could write a mock client implementing the Client interface.

 1type MockClient struct {
 2  t *testing.T
 3  expected *http.Request
 4  // ...
 5}
 6
 7func (m *MockClient) Do(req *http.Request) (*http.Response, error) {
 8  // Evaluate the request received with the expected request `m.expected`
 9  //...
10
11  return &http.Response{
12    StatusCode: 200,
13    Body:       ioutil.NopCloser(bytes.NewBufferString("OK")),
14  }, nil
15}

Now we can test the Endpoint function with the MockClient type as part of a unit test.