Interfaces and the Handle/Body Idiom (continued)

In response to a previous post, very insightfully, Steve Singer, pointed out that it is helpful to distinguish between three concepts, when talking about “interfaces” in general:

  • Data types
  • Capabilities
  • Facades

Data Types

These are your abstract data types (ADT) from CS101: stack, vector, deque, and so on — types that are defined by their interfaces. Their implementation is arbitrary (and encapsulated); all that matters is that they fulfill the contract implied by their interface.

(A side note about contracts and interfaces: the contract may extend beyond the visible function signatures. For example, a map or dictionary type may make guarantees about the time complexity to lookup an element for a given key, but this will not be visible in the function signature!)

Data types are an abstraction mechanism.

Capabilities

This is a less familiar concept. The idea is that a type has some ancillary, but useful, capability, that is typically somewhat independent (one might say: orthogonal) to the data types primary purpose. A very common capability is for an arbitrary object to turn itself into a human-readable string; a print() function might invoke a to_string() method on any one of its arguments. Other examples for such auxiliary capabilities include types that are able to serialize themselves to XML or JSON, or that can be compared (comparable) to other objects of the same type.

In object-oriented and in particular functional lingo, the same concept is often referred to as a trait. A related idea is the notion of a mixin. The terms are often used interchangeably, but the term “mixin” seems to imply that not only an interface, but also an appropriate implementation is added to a base type.

Capabilities are a modeling mechanism to enable polymorphism.

As such, they are only relevant in statically typed languages (because in dynamically typed languages polymorphic method invocations are resolved at runtime and do not need to be reflected in the type).

Facades

This is really a notion from the old Gang-of-Four Design Patterns book: some complicated subsystem is hidden behind a simplified Facade. The Facade is a real object in user space. It typically holds references to other, possible external, services and forwards calls to its own methods to the corresponding methods on the remote objects. A typical application is a database wrapper, a wrapper around a remote object store, or a naming service.

The facade typically has state (in that it holds references to the other services that it hides), but does not implement any business logic: it mostly passes control through to the various services that it hides. That being said, it is often a logical place to functionality that is largely unrelated to the business logic (such as caching, logging, or even authentication).

One important application of facades is that they allow to swap out the “real” backend for a mock backend, in order to allow testing. Used in this way, they provide a stable interface, while the actual implementation does vary.

Facades are an architecture structuring mechanism.

Interfaces and Go

How do these different “interface” concepts relate to the Go interface facility?

  • Go does not emphasize abstraction and encapsulation very strongly. For example, it discourages the use of accessor functions (getters and setters), and instead recommends accessing struct data members directly. Although it can be done, constructing non-trivial abstract data types is just not idiomatic Go, with interfaces or without.

    In my experience, the lack of inheritance makes polymorphism for domain objects (that is, objects with non-trivial, domain-oriented semantics) impractical — not impossible, but just not practical. What I typically want in a polymorphic domain object is to retain some of the functionality of the base type, and to override others. Go’s interfaces are no help.

  • Go’s interfaces probably come closest to the “capabilities” concept. Go interfaces are rather specifically designed to allow for polymorphism in a statically typed language, but requiring minimal effort on the part of the programmer: Go interfaces are fulfilled implicitly, without the need to update the implementing types.

  • Using Go’s interfaces as facades is clearly possible, and there don’t seem to be strong reasons against it, although this particular application seems to be different from the way they were intended to be used by Go’s designers (and are used, for example, in the Go standard library).

I think the main take away here is that Go’s interfaces are neither a strong abstraction mechanism, nor intended as a structuring technique, but mostly useful as a slick, implicit way to allow polymorphic method invocation in a statically typed language, without a lot of developer overhead.