La interfaz, como concepto general, establece un contrato de "qué comportamiento se debe tener" . Las clases que implementen una interfaz, deben cumplir con dicho contrato. Por otra parte, el contrato puede ser lógico o abstracto, en el sentido que el lenguaje no valida que la interfaz se cumpla, sino que es tarea del programador cumplir el contrato.
Es importante hacer esta distinción, debido a que javascript no está orientado en clases, sino en prototipos, así que el lenguaje no tiene recursos para representar y validar una "interfaz", en su lugar el programador debe encargarse, ya sea textualmente, a través de la documentación, o bien codificar dichas validaciones, sea cual sea la opción, el concepto de "interfaz" existe, mas no es un recurso del lenguaje.
The javascript way:
En javascript puedes obligar a las clases a cumplir una interfaz, actualmente existen dos formas muy conocidas, y no son excluyentes, a veces te servirá más una, que otra.
1. Ducktyping
Debido a que javascript es un lenguaje débilmente tipado, en lugar de comprobar si una clase implementa una interfaz, simplemente inspeccionas
las propiedades del objeto, para ver si cumple o no con la interfaz. En tu caso, Empleado, Cliente, implementan la interfaz Autenticable, que simplemente contiene un método, llamado autenticable. Lo normal es simplemente constar qué es un objeto Autenticable, y documentarlo dentro del código, en mi ejemplo se documentó el código con jsdocs, y se utilizo typedef, aunque esto es opcional, lo importante es que tú y tus compañeros sepan de qué trata un objeto Autenticable
/**
* @typedef {Object} Autenticable
* @property {function} autenticable
*/
class SistemaAutentificacion {
/**
* Inicia sesión
* @param {Autenticable} usuario a iniciar sesion
* @param {string} clave del usuario
*/
static login(usuario, clave) {
if (isAutenticable(usuario)) {
return usuario.autenticable(clave)
}
return false
}
}
function isAutenticable(auten) {
if (typeof(auten) === 'object' && typeof(auten.autenticable) === 'function') {
return true
}
return false
}
Luego podrías hacer cosas como
const usuario = { name: 'Eduen', autenticable: () => true }
SistemaAutentificacion.login(usuario, 'password') // true
Lo importante es notar que la interfaz pertenece al "objeto" y no a la clase, el método puede definirse como una propiedad propia del objeto, o bien pertenecer en el prototipado del mismo (heredado de una clase).
2. Prototipado
A diferencia del ducktyping, esta solución busca utilizar instanceof
como sistema de tipos, por ende, se crea una clase que represente la interfaz, y todas las clases que implementen la interfaz, deberán extender de esta clase, y sobreescribir las funciones de la interfaz.
class Autenticable {
autenticable(clave) {
throw new Error('sin implementar')
}
}
class Empleado extends Autenticable {
/* Override */
autenticable(clave) {
return (clave == this.#clave)
}
}
class Cliente extends Autenticable {
/*Override*/
autenticable(clave) {
return false
}
}
class SistemaAutentificacion {
static login(usuario, clave) {
if (usuario instanceof Autenticable) {
return usuario.autenticable(clave)
}
return false
}
}
Algunos ejemplos:
var obj1 = {autenticable: () => true}
var obj2 = new Autenticable()
var obj3 = new Empleado()
obj3.#clave = 'foo'
var obj4 = new Cliente()
SistemaAutentificacion.login(obj1, 'foo') // falso, obj1 no es instancia de Autenticable
SistemaAutentificacion.login(obj2, 'foo') // Error 'sin implementar', no puedes crear objetos utiles desde la interfaz
SistemaAutentificacion.login(obj3, 'foo') // true
SistemaAutentificacion.login(obj4, 'foo') // false
Espero haberte aclarado un par de dudas, saludos