implements, abstract classes y contratos en TypeScript | Nicolás GarzónTypeScript
interface ProductRepository {
findById(id: string): Promise<Product | undefined>;
save(product: Product): Promise<void>;
}
class InMemoryProductRepository
implements ProductRepository {
async findById(id: string) {
return products.get(id);
}
async save(product: Product) {
products.set(product.id, product);
}
}
implements no copia métodos ni cambia el prototipo.
TypeScript
interface Named {
name: string;
}
class User implements Named {
name = "Nicolás";
role = "admin";
}
La instancia sigue teniendo role; implements solo comprueba el mínimo requerido.
TypeScript
interface Cacheable {
cacheKey?(): string;
}
La clase no recibe cacheKey automáticamente ni puede usarlo sin declararlo.
TypeScript
class ProductService
implements Initializable, Disposable {
initialize(): void {}
dispose(): void {}
}
Puede ser útil cuando los contratos representan capacidades pequeñas.
TypeScript
interface Serializable {
serialize(): string;
}
implements comprueba instancias. No verifica miembros static ni la firma del constructor.
TypeScript
type SerializableConstructor = {
new (...args: any[]): Serializable;
fromJSON(value: string): Serializable;
};
La clase se comprueba como valor:
TypeScript
const Constructor: SerializableConstructor = Product;
TypeScript
abstract class Entity {
constructor(readonly id: string) {}
abstract validate(): void;
describe(): string {
return `Entity ${this.id}`;
}
}
No puede instanciarse directamente.
TypeScript
class Product extends Entity {
validate(): void {
if (this.id.length === 0) {
throw new Error("Invalid id");
}
}
}
TypeScript
abstract class DocumentModel {
abstract readonly type: string;
abstract get content(): string;
}
TypeScript
abstract class BaseService {
protected constructor(
protected readonly logger: Logger,
) {}
}
Solo subclases pueden invocarlo como parte de construcción.
- Solo contrato estático.
- No existe en runtime.
- Una clase puede implementar varias.
- Permite objetos literales y factories.
- Existe en runtime.
- Puede almacenar estado.
- Puede ofrecer implementación.
- Participa en herencia simple.
- Puede usarse con instanceof.
TypeScript
abstract class ProductRepository {
abstract save(product: Product): Promise<void>;
}
Puede funcionar, pero una interface es más flexible si no existe comportamiento o identidad runtime compartida.
TypeScript
abstract class Importer<T> {
async import(input: string): Promise<T[]> {
const rawItems = this.parse(input);
return rawItems.map((item) => this.validate(item));
}
protected abstract parse(input: string): unknown[];
protected abstract validate(value: unknown): T;
}
La base define el flujo y subclases completan pasos.
Este patrón acopla el algoritmo a herencia; composición con funciones también puede ser más simple.
Una clase puede cumplir una interface sin escribir implements.
TypeScript
const repository: ProductRepository = new CustomRepository();
implements aporta feedback dentro de la declaración de clase y documenta intención.
TypeScript
interface Clock {
now(): Date;
}
class SystemClock implements Clock {
now(): Date {
return new Date();
}
}
Un fake puede ser un objeto literal; no necesita heredar.
- Creer que implements añade código.
- Esperar que propiedades opcionales aparezcan en la clase.
- Usar implements para comprobar static members.
- Elegir abstract class cuando solo necesitas un contrato.
- Crear jerarquías para compartir dos líneas de código.
- Confundir abstract con interface runtime.
- Asumir que una clase debe declarar implements para ser compatible.
- Usar instanceof con una interface.
- implements comprueba el lado de instancia.
- No copia comportamiento ni cambia el tipo inferido de la clase.
- Interfaces no existen en runtime.
- Abstract classes pueden combinar estado, contrato e implementación.
- Una clase solo extiende una base, pero implementa varios contratos.
- El lado static necesita un contrato separado.
- La compatibilidad sigue siendo estructural salvo miembros privados/protected.
¿Por qué no puedes usar value instanceof ProductRepository cuando ProductRepository es una interface?
Respuesta
Porque la interface se elimina al compilar y no existe como constructor o valor durante ejecución.
Herencia, override y composición explica cómo tipar jerarquías sin confundir reutilización con diseño.