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.