-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
30: Practice Exercise: Modeling a Library System with Interfaces
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
IRenewablewith a methodRenew(int days). - Create a class
EBookthat implementsICatalogItem,IBorrowable, andIRenewable. - In the
EBookclass, implementRenewso that it prints a message confirming the extension of the loan. - Write a small piece of logic (in a
Mainmethod or a test class) that creates a list ofIRenewableitems and callsRenewon each of them.
There are no comments for now.