Advertisement
Intermediate C# Lesson 10 of 10 (Final)

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.

Advertisement

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 →