Lesson 10: Real-World OOP Project in C#
Congratulations! You've mastered the fundamentals of OOP in C#. This final lesson brings it all together with a Library Management System project. You'll apply encapsulation, inheritance, interfaces, abstract classes, and design patterns to build a real-world application.
Project Overview: Library Management System
We'll build a system to manage books and users in a library. Requirements:
- Store books with title, author, ISBN, and availability status
- Manage different user types (student, staff, faculty)
- Track borrowing and returning books with due dates
- Apply fines for late returns
- Use inheritance and interfaces for extensibility
Step 1: Define Core Classes
// Base class for all books
public abstract class Book
{
public string ISBN { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public bool IsAvailable { get; set; }
protected Book(string isbn, string title, string author)
{
ISBN = isbn;
Title = title;
Author = author;
IsAvailable = true;
}
public abstract decimal GetLateFee(int daysLate);
public override string ToString()
{
return $"{Title} by {Author} ({ISBN})";
}
}
// Specific book types
public class TextBook : Book
{
public int Edition { get; set; }
public TextBook(string isbn, string title, string author, int edition)
: base(isbn, title, author)
{
Edition = edition;
}
public override decimal GetLateFee(int daysLate)
{
return daysLate * 0.50m; // $0.50 per day
}
}
public class Novel : Book
{
public Novel(string isbn, string title, string author)
: base(isbn, title, author)
{
}
public override decimal GetLateFee(int daysLate)
{
return daysLate * 0.25m; // $0.25 per day
}
}
Step 2: User Management with Interface
// Interface for borrowable items
public interface IBorrowable
{
void Borrow(Book book);
void ReturnBook(Book book, DateTime returnDate);
decimal CalculateFines();
}
// Base user class
public abstract class User : IBorrowable
{
public string Name { get; set; }
public string UserId { get; set; }
protected List<Book> BorrowedBooks { get; set; }
protected User(string name, string userId)
{
Name = name;
UserId = userId;
BorrowedBooks = new List<Book>();
}
public virtual void Borrow(Book book)
{
if (book.IsAvailable && BorrowedBooks.Count < GetMaxBooks())
{
BorrowedBooks.Add(book);
book.IsAvailable = false;
Console.WriteLine($"{Name} borrowed {book.Title}");
}
}
public virtual void ReturnBook(Book book, DateTime returnDate)
{
if (BorrowedBooks.Remove(book))
{
book.IsAvailable = true;
var daysLate = (int)(returnDate - returnDate).TotalDays;
if (daysLate > 0)
{
decimal fine = book.GetLateFee(daysLate);
Console.WriteLine($"{Name} owes ${fine} in fines");
}
}
}
public abstract int GetMaxBooks();
public abstract decimal CalculateFines();
}
// Specific user types
public class Student : User
{
public Student(string name, string userId) : base(name, userId) { }
public override int GetMaxBooks() => 5;
public override decimal CalculateFines() => BorrowedBooks.Count * 1.50m;
}
public class Librarian : User
{
public Librarian(string name, string userId) : base(name, userId) { }
public override int GetMaxBooks() => 20;
public override decimal CalculateFines() => 0; // No fines for staff
}
Step 3: Library Service with Singleton
// Singleton pattern: only one library instance
public class LibraryService
{
private static LibraryService _instance;
private List<Book> _catalog;
private List<User> _users;
private LibraryService()
{
_catalog = new List<Book>();
_users = new List<User>();
}
public static LibraryService GetInstance()
{
if (_instance == null)
_instance = new LibraryService();
return _instance;
}
public void AddBook(Book book)
{
_catalog.Add(book);
Console.WriteLine($"Added: {book}");
}
public void RegisterUser(User user)
{
_users.Add(user);
}
public Book SearchByISBN(string isbn)
{
return _catalog.FirstOrDefault(b => b.ISBN == isbn);
}
public int GetAvailableBooks()
{
return _catalog.Count(b => b.IsAvailable);
}
}
// Usage
var library = LibraryService.GetInstance();
library.AddBook(new TextBook("978-0-134-49418-6", "Clean Code", "Robert Martin", 1));
library.AddBook(new Novel("978-0-06-112008-4", "To Kill a Mockingbird", "Harper Lee"));
var student = new Student("Alice", "S123");
library.RegisterUser(student);
💡 Key Takeaways: This project demonstrates abstract classes for hierarchy (Book types), interfaces for contracts (IBorrowable), polymorphism (different GetLateFee implementations), and the Singleton pattern for shared resources (library instance).
🧠 Quick Check — Final Lesson
What OOP principle allows us to treat Student and Librarian through the same User interface?
Series Summary & Next Steps
Encapsulation: Hide internal details with private/protected access modifiers.
Inheritance: Create hierarchies of classes sharing common behavior.
Polymorphism: Allow objects of different types to respond to the same call differently.
Abstraction: Expose only necessary details through abstract classes and interfaces.
Design Patterns: Use proven solutions like Singleton for common problems.
🎉 Congratulations!
You've completed the C# OOP series! You now understand the four pillars of OOP and can design clean, maintainable code. Next, explore ASP.NET Core to build real applications with these skills.
Explore More Tutorials →