Skip to Content
Course content

30: Practice Exercise: Modeling a Library System with Interfaces

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

By now, you know the syntax of interfaces, but the real challenge is knowing where to draw the line. Interfaces aren't just about forcing a class to have certain methods; they're about defining a contract for behavior. Today, we're going to model a library system. It sounds simple, but it's a perfect playground for practicing interface segregation.

Defining the Core Identity of a Library Item

Every single thing in our library—whether it's a paperback, a Blu-ray, or a rare 19th-century map—needs a way to be identified and searched. I'll start by creating a base interface that covers these universal traits. I call this ICatalogItem.

public interface ICatalogItem
{
    string Id { get; }
    string Title { get; }
    bool IsAvailable { get; }
}

I'm keeping this lean. I don't care how the item is stored or what its physical dimensions are; I just need to know it has an ID, a title, and a status.

Adding the Ability to be Borrowed

Now, here is where we need to be careful. Not everything in a library can be checked out. Some items are "Reference Only." If I put a Borrow() method inside ICatalogItem, I'm forcing my reference-only maps to implement a method they can't actually use. That's a smell. Instead, I'll create a separate interface for the behavior of borrowing.

public interface IBorrowable
{
    void Borrow(string user);
    void ReturnItem();
}

Wait, I messed up the state management

I'll be honest: when I first started sketching this out, I put the IsAvailable property inside IBorrowable instead of ICatalogItem. It seemed logical because only borrowable things have a "borrowed" state. But then I realized that from a catalog perspective, a reference book is "available" to be read in the building, but not "available" to be taken home.

By keeping IsAvailable in ICatalogItem, I can use it for a general search UI. The IBorrowable interface should only handle the action of changing that state. Let's see how this looks when we actually implement these in a class.

Bringing it all together with concrete classes

Let's build a Book class. Since a book can be searched and can be borrowed, it implements both interfaces. Then, I'll create a ReferenceManual that only implements ICatalogItem.

public class Book : ICatalogItem, IBorrowable
{
    public string Id { get; set; }
    public string Title { get; set; }
    public bool IsAvailable { get; private set; } = true;

    public void Borrow(string user)
    {
        if (!IsAvailable) throw new InvalidOperationException("Already borrowed!");
        IsAvailable = false;
        Console.WriteLine($"{Title} has been borrowed by {user}.");
    }

    public void ReturnItem()
    {
        IsAvailable = true;
        Console.WriteLine($"{Title} has been returned.");
    }
}

public class ReferenceManual : ICatalogItem
{
    public string Id { get; set; }
    public string Title { get; set; }
    public bool IsAvailable { get; set; } = true; // Always true, essentially
}

Handling different types through a single lens

The real magic happens when we write logic that doesn't care about the specific class, only the interface. I want a method that can process a return for anything borrowable, regardless of whether it's a book, a DVD, or a laptop.

public class LibraryManager
{
    public void ProcessReturn(IBorrowable item)
    {
        // I don't need to know if this is a Book or a DVD.
        // I just know it supports the ReturnItem contract.
        item.ReturnItem();
    }
}

If I tried to pass a ReferenceManual into ProcessReturn, the compiler would stop me immediately. That's the safety net interfaces provide. You aren't just organizing code; you're encoding the rules of your business domain directly into the type system.




📋 Practical Task

Exercise: Implementing Renewable Digital Media

Your goal is to extend the library system to support digital licenses (like e-books). Digital items have different rules than physical ones: they can't be "returned" in the traditional sense, but they can be "renewed" to extend the loan period.

  • Create a new interface called IRenewable with a method Renew(int days).
  • Create a class EBook that implements ICatalogItem, IBorrowable, and IRenewable.
  • In the EBook class, implement Renew so that it prints a message confirming the extension of the loan.
  • Write a small piece of logic (in a Main method or a test class) that creates a list of IRenewable items and calls Renew on each of them.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.