This is an exercise I solved in the PPP book in C++. If there's a better way I could have done something, please tell me. (No main() and Date (just year)) Thank you!
For context here is the exercise:
This exercise and the next few require you to design and implement a Book class, such as you can imagine as part of software for a library. Class
Book
should have members for the ISBN, title, author, and copyright date. Also store data on whether or not the book is checked out. Create functions for returning those data values. Create functions for checking a book in and out. Do simple validation of data entered into aBook
; for example, accept ISBNs only of the form n-n-n- x where n is an integer and x is a digit or a letter. Store an ISBN as a string.Add operators for the
Book
class. Have the==
operator check whether the ISBN numbers are the same for two books. Have!=
also compare the ISBN numbers. Have a<<
print out the title, author, and ISBN on separate lines.Create an enumerated type for the
Book
class calledGenre
. Have the types be fiction, nonfiction, periodical, biography, and children. Give each book aGenre
and make appropriate changes to theBook
constructor and member functions.Create a
Patron
class for the library. The class will have a user’s name, library card number, and library fees (if owed). Have functions that access this data, as well as a function to set the fee of the user. Have a helper function that returns a Boolean (bool
) depending on whether or not the user owes a fee.Create a
Library
class. Include vectors ofBook
s andPatron
s. Include a struct calledTransaction
. Have it include aBook
, aPatron
, and aDate
from the chapter. Make a vector ofTransaction
s. Create functions to add books to the library, add patrons to the library, and check out books. Whenever a user checks out a book, have the library make sure that both the user and the book are in the library. If they aren’t, report an error. Then check to make sure that the user owes no fees. If the user does, report an error. If not, create aTransaction
, and place it in the vector ofTransaction
s. Also write a function that will return a vector that contains the names of allPatron
s who owe fees.
My solution:
Book.hpp:
#include <array>
#include <string>
#include <stdexcept>
class Book {
public:
enum class Genre {
fiction,
nonfiction,
periodical,
biography,
children,
genre_size
};
public:
// Book() = default; Doesn't make logical sense to have a default constructor here.
Book(const std::string& isbn, const std::string& title, const std::string& author, std::size_t copyRightYear, Genre genre);
const std::string& getIsbn() const { return m_isbn; }
const std::string& getTitle() const { return m_title; }
const std::string& getAuthor() const { return m_author; }
std::size_t getCopyRightYear() const { return m_copyRightYear; }
const std::string& getGenre() const {
static const std::array<std::string, static_cast<std::size_t>(Genre::genre_size)> genres {
"fiction",
"nonfiction",
"periodical",
"biography",
"children"
};
return genres.at(static_cast<std::size_t>(m_genre));
}
void bookCheckIn();
void bookCheckOut();
bool isCheckedOut() const { return m_isCheckedOut; }
private:
bool validateIsbn(const std::string &isbn) const; // n-n-n-x where n is an integer and x is a digit or a letter
private:
std::string m_isbn;
std::string m_title;
std::string m_author;
std::size_t m_copyRightYear;
Genre m_genre;
bool m_isCheckedOut{};
};
bool operator==(const Book& lhs, const Book& rhs);
bool operator!=(const Book& lhs, const Book& rhs);
std::ostream& operator<<(std::ostream& os, const Book& book);
Book.cpp
#include "Book.hpp"
#include <iostream>
#include <algorithm>
#include <cctype>
Book::Book(const std::string& isbn, const std::string& title, const std::string& author, std::size_t copyRightYear, Genre genre)
: m_isbn{ isbn },
m_title{ title },
m_author{ author },
m_copyRightYear{ copyRightYear },
m_genre{ genre },
m_isCheckedOut{ true }
{
if (!validateIsbn(isbn))
throw std::runtime_error{"Book::Book(): Invalid isbn."};
}
void Book::bookCheckIn() {
if (!isCheckedOut()) {
std::cout << "You have not checked out any book.\n";
return;
}
m_isCheckedOut = false;
}
void Book::bookCheckOut() {
if (isCheckedOut()) {
std::cout << "You already checked out a book.\n";
return;
}
m_isCheckedOut = true;
}
bool Book::validateIsbn(const std::string& isbn) const {
if (isbn.length() != 7)
return false;
for (int i{}; i < 7; i += 2) {
char currentChar{ isbn[i] };
if (!isdigit(currentChar) && !isalpha(currentChar))
return false;
}
if (isbn[1] != '-' || isbn[3] != '-')
return false;
char lastChar{ isbn[6] };
if (!isdigit(lastChar) && !isalpha(lastChar))
return false;
return true;
}
bool operator==(const Book& lhs, const Book& rhs) {
return lhs.getIsbn() == rhs.getIsbn();
}
bool operator!=(const Book& lhs, const Book& rhs) {
return !(lhs == rhs);
}
std::ostream& operator<<(std::ostream& os, const Book& book) {
os << "Title: " << book.getTitle() << '\n'
<< "Author: " << book.getAuthor() << '\n'
<< "ISBN: " << book.getIsbn() << '\n'
<< "Is checked out? " << std::boolalpha << book.isCheckedOut() << '\n';
return os;
}
Patron.hpp
#include "Book.hpp"
#include <ostream>
class Patron {
public:
// Patron() = default; again no need for a default constructor here
Patron(const std::string& name, const std::string& libraryCardNumber);
const std::string& getName() const { return m_name; }
const std::string& getCardNumber() const { return m_libraryCardNumber; }
double getFees() const { return m_libraryFees; }
void setFees(double fees);
bool owesLibraryFees() const { return m_libraryFees > 0.0; }
private:
std::string m_name;
std::string m_libraryCardNumber;
double m_libraryFees{};
};
std::ostream& operator<<(std::ostream& os, const Patron& patron);
Patron.cpp
#include "Patron.hpp"
#include <stdexcept>
Patron::Patron(const std::string& name, const std::string& libraryCardNumber)
: m_name{ name }, m_libraryCardNumber{ libraryCardNumber } { }
void Patron::setFees(double fee) {
if (fee <= 0.0)
throw std::runtime_error{"setFee(double fee): parameter fee was <= 0.0."};
m_libraryFees = fee;
}
std::ostream& operator<<(std::ostream& os, const Patron& patron) {
os << "Name: " << patron.getName() << '\n'
<< "library Card Number: " << patron.getCardNumber() << '\n'
<< "Fees: " << '$' << patron.getFees() << '\n';
return os;
}
Library.hpp
#include "Book.hpp"
#include "Patron.hpp"
#include <iostream>
#include <vector>
class Library {
public:
struct Transaction {
Book book;
Patron patron;
};
public:
Library() = default;
void addBook();
void addPatron();
void checkBookOut();
const Book& getBook(const std::string& title) const;
const Patron& getPatron(const std::string& libraryCardNumber) const;
const std::vector<std::string> getPatronsWithFees() const;
private:
Book::Genre parseGenre(const std::string &genre) const;
bool checkPatron(const std::string& name, const std::string& cardNumber) const;
bool checkBook(const std::string& book) const;
private:
std::vector<Book> m_books;
std::vector<Patron> m_patrons;
std::vector<Transaction> m_transactions;
};
Library.cpp
#include "Library.hpp"
#include <iostream>
Book::Genre Library::parseGenre(const std::string& genre) const {
Book::Genre parsedGenre;
if (genre == "fiction")
parsedGenre = Book::Genre::fiction;
else if (genre == "nonfiction")
parsedGenre = Book::Genre::nonfiction;
else if (genre == "periodical")
parsedGenre = Book::Genre::periodical;
else if (genre == "biography")
parsedGenre = Book::Genre::biography;
else if (genre == "children")
parsedGenre = Book::Genre::children;
else {
throw std::runtime_error("Invalid genre: " + genre);
}
return parsedGenre;
}
bool Library::checkPatron(const std::string& name, const std::string& cardNumber) const {
for (const auto& patron : m_patrons) {
if (patron.getName() == name && patron.getCardNumber() == cardNumber) return true;
}
return false;
}
bool Library::checkBook(const std::string& book) const {
for (const auto& b : m_books) {
if (b.getTitle() == book && !b.isCheckedOut()) return true;
}
return false;
}
void Library::addBook() {
std::cout << "Please enter a book in the database...\n";
std::cout << "Enter the isbn: ";
std::string isbn;
std::cin >> isbn;
std::cin.ignore();
std::cout << "Enter the title: ";
std::string title;
std::getline(std::cin, title);
std::cout << "Enter the author: ";
std::string author;
std::getline(std::cin, author);
std::cout << "Enter the copy right year: ";
std::size_t copyRightYear{};
std::cin >> copyRightYear;
std::cout << "Enter genre: ";
std::string genre;
std::cin >> genre;
m_books.emplace_back(isbn, title, author, copyRightYear, parseGenre(genre));
}
void Library::addPatron() {
std::cout << "Please enter a Patron in the database...\n";
std::cout << "Enter your name: ";
std::string name;
std::getline(std::cin, name);
std::cout << "Enter library card number: ";
std::string libraryCardNumber;
std::cin >> libraryCardNumber;
m_patrons.emplace_back(name, libraryCardNumber);
}
void Library::checkBookOut() {
std::cout << "Enter your name and your library card number: ";
std::string name, libraryCardNumber;
std::getline(std::cin, name);
std::getline(std::cin, libraryCardNumber);
if (!checkPatron(name, libraryCardNumber))
throw std::runtime_error{"checkBookOut(): person doesn't exist in the database."};
std::cout << "Enter book title: ";
std::string title;
std::getline(std::cin, title);
if(!checkBook(title))
throw std::runtime_error{"checkBookOut(): book doesn't exist in the database."};
Book book{ getBook(title) };
book.bookCheckOut();
const Patron& patron{ getPatron(libraryCardNumber) };
if (patron.owesLibraryFees())
throw std::runtime_error{"checkBookOut(): patron owns fees."};
m_transactions.push_back({ book, patron });
}
const Book& Library::getBook(const std::string& title) const {
for (const auto& book : m_books)
if (book.getTitle() == title)
return book;
}
const Patron& Library::getPatron(const std::string& libraryCardNumber) const {
for (const auto& patron : m_patrons)
if (patron.getCardNumber() == libraryCardNumber)
return patron;
}
const std::vector<std::string> Library::getPatronsWithFees() const {
std::vector<std::string> names;
names.reserve(m_patrons.size());
for (const auto& patron : m_patrons) {
if (patron.owesLibraryFees())
names.emplace_back(patron.getName());
}
return names;
}