CodeGym /Courses /C# SELF /Interface Contracts

Interface Contracts

C# SELF
Level 23, Lesson 3
Available

1. Introduction

So, we've seen that interfaces are powerful contracts. But what exactly is programming at the interface level? It's a design philosophy that says: "Depend on abstractions, not on concrete implementations."

Let's use an analogy again. You want to make coffee. You buy a coffee machine. You don't really care if it's a specific model like "Super-Duper-Machine V3000" or "Mega-Automat Barista 5000", right? What matters is that this device can "make coffee" – that is, it implements the "contract" ICoffeeMaker. You just press the "Make Coffee" button, and it does its thing. How it does it – with ground beans, capsules, or whatever – you as a user don't really care.

In code, this means that instead of requiring a specific type like Document or Image in your methods, you'll require IPrintable. Your code will work with any object that implements this interface, without knowing its actual type.

An interface contract is a guarantee that any class implementing the interface will definitely provide definitions for all interface members (methods, properties, events). From the program's point of view, any object implementing the interface supports a certain "set of capabilities", and you can rely on that, even if you have no clue how it's actually done inside.

Let's say you and your coworker agree to always meet by the red couch in the lobby. How he gets there – by elevator, on foot, or running up the walls – doesn't matter. The main thing: he'll be there. That's what an interface is – it's the "red couch" for your code.

Example


public interface IPrintable
{
    void Print();
}

Contract: anyone who implements IPrintable must be able to print itself to the screen. How – that's up to them.

2. Contract as a Touchpoint Between System Parts

How it works in practice

A system can have dozens of classes, made by different people, at different times, for different reasons. But if they all stick to one contract (i.e., implement the same interface), you can use them in a unified way.

Real-world example: imagine a customer database. There's a Customer class, an Employee class, a Contractor class. Each stores info in its own way, but if they all implement, say, the IContactInfo interface, which requires GetEmail() and GetPhoneNumber() methods, then outside code doesn't care what type the object is – the main thing is you can get the email and phone.


public interface IContactInfo
{
    string GetEmail();
    string GetPhoneNumber();
}

public class Customer : IContactInfo
{
    public string Email { get; set; }
    public string Phone { get; set; }

    public string GetEmail() => Email;
    public string GetPhoneNumber() => Phone;
}

// Same for Employee and Contractor...

Now, if you need to print info for everyone your company has contact with (doesn't matter who they are), you just loop through a list of IContactInfo, call the needed methods – and it all works.

3. Programming "at the Interface Level"

Programming at the interface level means writing code that depends not on concrete classes, but only on interfaces (i.e., contracts). A class that implements an interface can be anything, as long as it meets the interface's requirements.

Why does it matter?

  • Scalability: it's easy to add new types without changing existing code.
  • Testability: you can easily swap objects for mocks (stubs) during testing.
  • Flexibility: the implementation can change every day, the interface stays stable.
  • Clean architecture: your modules are loosely coupled, so you can reuse them.

Example — Familiar Program: "Bank Account"

In previous app examples, we had an abstract BankAccount class with an abstract Withdraw() method, and concrete types (SavingsAccount, CheckingAccount) implemented the details.

Now let's add an interface – for example, to output balance info:


public interface IBalanceReporter
{
    void ReportBalance();
}

public abstract class BankAccount : IBalanceReporter
{
    public double Balance { get; set; }

    public abstract void Withdraw(double amount);

    public void ReportBalance()
    {
        Console.WriteLine($"Current balance: {Balance} euros");
    }
}

Now anyone working with IBalanceReporter can call ReportBalance() no matter what kind of account it is.

4. General Processing and Polymorphism via Contract

Example: General Handler

As soon as we work with an interface, we can make generic methods that don't care about the object's type:


static void PrintAllBalances(IBalanceReporter[] accounts)
{
    foreach (var reporter in accounts)
    {
        reporter.ReportBalance();
    }
}

This list can include any objects that implement IBalanceReporter: SavingsAccount, CheckingAccount, even something like MockAccountForTesting. No magic, it just works.

Diagram: What an Interface Contract Gives You


+-------------------+      implements     +-------------------+
|   BankAccount     |  <---------------- |  IBalanceReporter |
|  (SavingsAccount) |                    |-------------------|
|  (CheckingAccount)|                    | + ReportBalance() |
+-------------------+                    +-------------------+
         |                                   ^
         |                                   |
         +-----------+-----------------------+
                     |
        Any other class implementing the contract
Diagram: interface contract as a touchpoint

5. Contracts, Business Logic, and Writing Architecture

An interface contract clearly separates what a class must be able to do from how it does it. That's why software architects love to design system logic through interfaces. Often, the interface (contract) is designed first, and its implementation comes later.

Real-life Example: Payment Systems

E-wallets, cards, PayPal, crypto... Each has its own details, but if you abstract and make an IPaymentProvider interface:


public interface IPaymentProvider
{
    void Pay(decimal amount);
    bool Refund(decimal amount);
}

Code that interacts with this interface doesn't care if it's paying from a card or an account. It's convenient for architecture and for life: you can add support for new payment systems without touching the rest of the code.

6. Extracting Business Logic into a Contract

A contract lets you move the main business rules up to the interface level, and leave the details (like limit checks, cashback, etc.) to the specific classes.

Another Example


public interface ILogger
{
    void LogInfo(string message);
    void LogError(string message);
}

public class ConsoleLogger : ILogger
{
    public void LogInfo(string message)  => Console.WriteLine($"INFO: {message}");
    public void LogError(string message) => Console.WriteLine($"ERROR: {message}");
}

public class FileLogger : ILogger
{
    public void LogInfo(string message)  => /* Write to file */;
    public void LogError(string message) => /* Write to file */;
}

Then client code looks like this (doesn't matter which logger it's using):


void DoWork(ILogger logger)
{
    logger.LogInfo("Work started.");
    // ... some work ...
    logger.LogError("Work went wrong.");
}

This is what programming at the interface level is all about: client code depends only on the contract (interface), not on the specific implementation.

7. Mistakes and Typical Traps

Beginners often make this mistake: their code is tightly coupled to concrete classes, not interfaces. This leads to a bunch of problems:

  • It's hard to swap or test things – you have to rewrite a lot of code.
  • You can't add new types without changing code in lots of places.
  • Modules end up being tightly "glued" to each other.

The opposite – if you build your system on interfaces from the start and pass parameters through them, you get loose coupling and flexibility.

Main tip: try to think of module interaction as interaction between contracts, not between concrete implementations.

2
Task
C# SELF, level 23, lesson 3
Locked
Programming at the Interface Level
Programming at the Interface Level
2
Task
C# SELF, level 23, lesson 3
Locked
Universal handler via interfaces
Universal handler via interfaces
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION