The interface exposes behaviors. You invoke behaviors to do something. You don’t know how the interface does it, all you know is that it promises to do something, and that under non-exceptional circumstances that it will deliver.

This is what I think of when I think Object Oriented Programing: Behavior as a First Class citizen.

interface ServerLocator {
  locate(key: string): string;
}

We are not saying how to locate the server, we are just declaring the behavior that should happen. Implementation details are not important.

Given a key, give me a server

It wouldn’t matter if you implemented:

  • Rendezvous hashing
  • Consistent hashing
  • Lookup table
  • Hardcoding the value…

This is how it should be:

  • Interfaces as contracts of intent.
  • Swappable behaviors behind those contracts.
  • Encapsulation as a means of hiding reasons, not fields.

This is in direct opposition to how it’s done in procedural programming:

Do this, then that, then this.

In OOP, the focus is on behavior:

Here’s something that knows how to do a thing

You’re not saying how the work should be done. You are only declaring what work should be done, and trusting the interface to deliver as per its promise. That’s the key difference.

Procedural

function getServer(key: string, servers: string[]): string {
  let max = -Infinity;
  let chosen = "";
  for (const server of servers) {
    const score = hash(server + key);
    if (score > max) {
      max = score;
      chosen = server;
    }
  }
  return chosen;
}

Object Oriented

interface ServerLocator {
  locate(key: string): string;
}
 
class RendezvousLocator implements ServerLocator {
  constructor(private servers: string[]) {}
 
  locate(key: string): string {
    // implementation hidden
  }
}

Incidentally, this has led me to feel that functional programming is just a different way of treating behavior as a first-class citizen.

Just a different expression of the same idea. But I don’t know that much about functional programming, so I could be wrong…