💾 Archived View for ser1.net › post › the-power-of-ad-hoc-interfaces.gmi captured on 2024-06-16 at 12:40:02. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2024-03-21)
-=-=-=-=-=-=-
--------------------------------------------------------------------------------
title: "The power of ad-hoc interfaces" date: 2012-10-02T06:48:00Z tags: ["golang", "programming", "software"]
--------------------------------------------------------------------------------
That's not a technical term; it doesn't *mean* anything by itself. But since Go doesn't have a term (or I'm not aware of it) for the particular flavor of type interfaces it supports, I'm calling Go's interfaces "ad-hoc interfaces."
I keep being tickled by Go's submarine features. These are things which aren't touted with any fanfare, but which I find more useful as I gain experience with the language. In this case, the feature I'm growing increasingly fond of is the fact that types are not tightly coupled to interfaces; this is in contrast to Java objects, which are tightly coupled to interfaces.
For example, in Java:
public interface Door { public void open(); public void close(); public boolean isOpen(); } public class Cupboard implements Door { public void open() { ... } ... }
Right? In Go, this might be:
type Door interface { Open() Close() IsOpen() bool } type Cupboard struct { } func (c *Cupboard) Open() { ... }
Notice that, in Go, the definition of `Cupboard` doesn't say anything about its relationship to `Door`. This is a compile time binding, a strict duck-typing mechanism. What's cool about this is that you can do this sort of post-definition ad-hoc type-to-interface binding anywhere. In a package that uses the "containers" library, you could say:
type Openable interface { Open() } func doSomething(o Openable) { o.Open() } func main() { c := new(containers.Cupboard) doSomething(c) f := new(filesystem.File) doSomething(f) o := new(icfp.Conference) doSomething(o) }
Those other libraries do not know, nor care, that you've defined an `Openable` interface, and (in this case) you don't care what other functions those library types expose other than `Open()`. In the Java version, you'd have three options:
1. Modify the libraries you're using and add the Openable interface to the class definitions
2. Use reflection to get a handle on the open() method
3. Use the visitor pattern: wrap the classes you want to use in another class that knows about each of the classes you're using and can type-switch on them
Solving this problem in traditional inheritance and is-a models is do-able, but much more tedious. This drives some unfortunate behaviors, one of them being that developers used to traditional OO (ok, I see this a lot in Java developers; I don't know if it afflicts C++ or .NET developers too) tend to over-engineer interfaces. In an attempt to avoid the pain of having go back and alter the original interface or class definitions, devs tend to try to think about all possible edge cases, and throw everything they can think of into the initial interface definition. Go drives a slightly different, and in my opinion, more natural, behavior. In Go, interfaces tend to evolve. The development tends to be more: "hey, a bunch of my classes are doing the same sort of thing; I've defined `Open()` for four classes already; maybe I could use an interface here and refactor some code into a common function."
Ad-hoc interfaces help developers avoid situations where functions are added to types just to ensure that the type satisfies an interface. They can help prevent eager, wasteful, function definitions. Most importantly, they allow developers to follow the rule that the interface belongs to the user, not the library. It allows developers to say "I use this functionality from this library," and if multiple libraries use similar naming conventions, then also "from these libraries."
If there is one, most important take-away from this, it is that *ad-hoc interface support is a tool that can be used to reduce efferent dependencies*. If the package defines, itself, the interfaces it uses, then the coupling to libraries that it uses is tenuous in the extreme, and this is a very good thing. It isn't that you can't achieve this level of abstraction in C++, or Java, or .NET; it's just that it's much harder to do in those languages because they have traditional, tightly coupled, is-a definition mechanisms.
There's a pretty good article about coupling metrics[1] over at IBM; it's interesting to read it with ad-hoc interfaces in mind.
1: http://www.ibm.com/developerworks/java/library/j-cq04256/