Skip to Content
Course content

147: Class Implements Clauses

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this happen in a dozen different projects: a developer creates a few classes that do similar things, and they assume that because the classes "look" the same, they can be used interchangeably. It works fine until the project grows, a new class is added, and suddenly you're hitting TypeError: x is not a function in production.

Take a look at this logging setup. It seems harmless enough at first glance.

class ConsoleLogger {
    log(message: string) {
        console.log(`[Console]: ${message}`);
    }
}

class FileLogger {
    log(message: string) {
        // Imagine logic here to write to a file
        console.log(`[File]: ${message}`);
    }
}

function processData(logger: ConsoleLogger | FileLogger) {
    logger.log("Processing started...");
    // Wait, I remember FileLogger has a 'saveToDisk' method, 
    // but does ConsoleLogger? Let's try to use a specialized method.
    (logger as any).flush(); 
}

const myLogger = new ConsoleLogger();
processData(myLogger);

The fragility of "Implicit" contracts

The problem here isn't just the as any (though that's a red flag). The real issue is that ConsoleLogger and FileLogger have no formal agreement. They both happen to have a log method, but there's nothing stopping someone from renaming log to write in FileLogger, or forgetting to add a required method to a new CloudLogger class.

When we rely on classes just "happening" to have the same methods, we're playing a dangerous game. We're relying on structural similarity without any enforcement. If you're building a system where different classes need to be swapped out—like switching from a mock database to a real one—you need a contract.

Enforcing the contract with implements

This is where the implements clause comes in. Instead of hoping our classes match, we define an interface that describes exactly what a "Logger" must be able to do. Then, we tell the class to implement that interface.

interface Logger {
    log(message: string): void;
    flush(): void;
}

class ConsoleLogger implements Logger {
    log(message: string) {
        console.log(`[Console]: ${message}`);
    }

    flush() {
        console.log("Flushing console buffer...");
    }
}

class FileLogger implements Logger {
    log(message: string) {
        console.log(`[File]: ${message}`);
    }

    flush() {
        console.log("Writing remaining logs to disk...");
    }
}

function processData(logger: Logger) {
    logger.log("Processing started...");
    logger.flush(); // Now this is type-safe!
}

Notice the shift. By adding implements Logger, we've turned a "suggestion" into a requirement. If I try to create a CloudLogger and forget the flush method, TypeScript will scream at me immediately. I don't have to wait for a runtime crash to realize I missed a method.

One thing to keep in mind: implements only checks the public side of your class. It doesn't care about your private methods or internal logic; it only cares that the external API matches the interface. It's also important to remember that implements doesn't actually provide any code. Unlike extends, which inherits behavior, implements is purely a check. You still have to write the actual method bodies in every class.




📋 Practical Task

Building a Pluggable Payment Gateway

You are building an e-commerce system that needs to support multiple payment providers (e.g., Stripe and PayPal). Currently, the code is inconsistent, and the main checkout function is crashing because some providers are missing the refund method.

Your Task:

  • Create an interface called PaymentGateway that requires two methods: processPayment(amount: number): boolean and refundPayment(transactionId: string): boolean.
  • Create a class StripeGateway that implements PaymentGateway.
  • Create a class PayPalGateway that implements PaymentGateway.
  • Write a function executeTransaction(gateway: PaymentGateway, amount: number) that calls processPayment.
  • Ensure that if you intentionally remove one of the required methods from either class, TypeScript flags it as an error.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.