Object Oriented Library Management System
Il sistema di gestione della libreria è un programma orientato agli oggetti che si occupa della pulizia di base di una libreria. Questa è la terza parte di una serie. La prima iterazione del progetto si trova qui e la seconda iterazione si trova anche qui
Gli attori principali sono il Bibliotecario, il Membro e il Sistema.
Maggiore preoccupazione
Ho fatto amicizia membro e bibliotecario della classe di sistema e ho anche composto la classe di sistema con bibliotecario e membro. Ho sentito che "System è composto da un Bibliotecario e membri" suona meglio di "System è un amico di un Bibliotecario e membri". Questo segue i modelli di progettazione comuni?
Nel mio post precedente, non mi è stato chiarito abbastanza bene il motivo per cui uno std :: vector dovrebbe essere preferibile rispetto a std :: list in questa situazione a causa del fatto che il sistema eseguirà spesso l'inserimento. Std :: vector sarebbe più scalabile e versatile tenendo in considerazione la velocità e l'efficienza?
Si può segnalare qualsiasi altra osservazione su potenziali insidie, trappole e cattive pratiche comuni.
Data.hh
#ifndef DATE_HH
#define DATE_HH
class Date {
friend std::ostream &operator<<( std::ostream &, const Date & );
private:
/* data-members */
unsigned month = 1;
unsigned day = 1;
unsigned year = 1970;
/* utility functions */
bool validateDate( unsigned m, unsigned d = 1, unsigned y = 1970 );
bool checkDay( unsigned m, unsigned d, unsigned y ) const;
bool isLeapYear( unsigned y ) const { return ( y % 400 == 0 ) || ( y % 4 == 0 && y % 100 != 0 ); }
public:
static constexpr unsigned int monthsPerYear = 12;
/* ctors */
Date() = default;
Date( unsigned m, unsigned d, unsigned y );
Date( unsigned m );
Date( unsigned m, unsigned d );
/* copy operations */
Date( const Date &d ) = default;
Date &operator=( const Date &d ) = default;
/* equality test operations */
bool operator==( const Date &d ) const;
bool operator!=( const Date &d ) const { return !( *this == d ); }
/* method-functions */
void setDate( unsigned m = 1, unsigned d = 1, unsigned y = 1970 );
unsigned getMonth() const;
unsigned getDay() const;
unsigned getYear() const;
void nextDay();
const std::string toString() const;
// dtor
~Date(){};
};
#endif
Date.cc
#include <iostream>
#include <string>
#include <stdexcept>
#include <array>
#include "../headers/Date.hh"
Date::Date( unsigned m, unsigned d, unsigned y ) {
if ( validateDate(m, d, y ) ) {
month = m; day = d; year = y;
}
}
Date::Date( unsigned m ) {
if( validateDate( m ) )
month = m;
}
Date::Date( unsigned m, unsigned d ) {
if ( validateDate( m, d ) ) {
month = m; day = d;
}
}
void Date::setDate( unsigned m, unsigned d, unsigned y ) {
if ( validateDate( m, d, y ) ) {
month = m; day = d; year = y;
}
}
void Date::nextDay() {
day += 1;
try {
checkDay( month, day, year );
} catch ( std::invalid_argument &e ) {
month += 1;
day = 1;
}
if ( month % 12 == 0 ) {
year += 1;
month = 1;
}
}
bool Date::operator==( const Date &d ) const {
if( month != d.month ) return false;
if ( day != d.day ) return false;
if ( year != d.year ) return false;
return true;
}
std::ostream &operator<<( std::ostream &os, const Date &d ) {
os << d.month << "/" << d.day << "/" << d.year;
return os;
}
// utility function
bool Date::validateDate( unsigned m, unsigned d, unsigned y ) {
// validate month
if ( m < 1 || m >= 13 )
throw std::invalid_argument( "Month must be between 1-12" );
// validate day
if ( checkDay( m, d, y ) == false )
throw std::invalid_argument( "Invalid day for current month and year" );
// validate year
if ( y < 1970 )
throw std::invalid_argument( "year must be greater than 1969" );
return true;
}
const std::string Date::toString() const {
return std::to_string(month) + "/" + std::to_string(day) + "/" + std::to_string(year);
}
bool Date::checkDay( unsigned testMonth, unsigned testDay, unsigned testYear ) const {
static const std::array < unsigned, monthsPerYear + 1 > daysPerMonth = { 0,31,28,31,30,31,30,31,31,30,32,30,31};
if ( testDay > 0 && testDay <= daysPerMonth[ testMonth ] )
return true;
if ( testMonth == 2 && testDay == 29 && isLeapYear( testYear ) )
return true;
return false;
}
BookItem.hh
#ifndef BOOKITEM_HH
#define BOOKITEM_HH
#include <iostream>
#include <string>
#include <string_view>
#include "Date.hh"
enum class BookStatus : unsigned { RESERVED, AVAILABLE, UNAVAILABLE, REFERENCE, LOANED, NONE };
enum class BookType : unsigned { HARDCOVER, MAGAZINE, NEWSLETTER, AUDIO, JOURNAL, SOFTCOPY };
class BookItem {
friend std::ostream &operator<<( std::ostream &, const BookItem & );
private:
/* data-members */
std::string title;
std::string author;
std::string category;
Date pubDate;
std::string isbn;
BookStatus status;
BookType type;
/* user connected to this book */
std::string bookcurrentUser;
public:
/* ctors */
BookItem() = default;
BookItem( const std::string &title, const std::string &author, const std::string &cat, const Date &pubDate, \
const std::string &isbn, const BookType type, const BookStatus status = BookStatus::AVAILABLE );
bool operator==( const BookItem &bookItem ) const;
bool operator!=( const BookItem &bookItem ) const { return !( *this == bookItem); };
/* method-functions */
void setStatus( BookStatus s ) { status = s; };
void setType( BookType t ) { type = t;};
void setCategory( const std::string &c ) { category = c; }
void setBookCurrentUser( std::string userName ) { bookcurrentUser = userName; }
std::string_view getBookCurrentUser() const { return bookcurrentUser; }
std::string_view getStatus() const;
std::string_view getType() const;
std::string_view getTitle() const { return title; }
std::string_view getAuthor() const { return author; }
std::string_view getCategory() const { return category; };
std::string_view getIsbn() const { return isbn; }
Date &getPubDate() { return pubDate; }
void printPubDate() const { std::cout << pubDate; }
const BookStatus getStatusByEnum() const { return status; }
const BookType getTypeByEnum() const { return type; }
// dtor
~BookItem() = default;
};
#endif
BookItem.cc
#include <iostream>
#include "../headers/BookItem.hh"
BookItem::BookItem( const std::string &t, const std::string &a, const std::string &c, const Date &d, \
const std::string &i, const BookType ty, const BookStatus s ) {
title = t, author = a, category = c, pubDate = d, isbn = i;
setStatus( s );
setType( ty );
}
bool BookItem::operator==( const BookItem &bookItem ) const {
if ( title != bookItem.title ) return false;
if ( author != bookItem.author ) return false;
if ( category != bookItem.category ) return false;
if ( pubDate != bookItem.pubDate ) return false;
if ( isbn != bookItem.isbn ) return false;
if ( status != bookItem.status ) return false;
if ( type != bookItem.type ) return false;
return true;
}
std::string_view BookItem::getStatus() const {
switch( status ) {
case BookStatus::AVAILABLE:
return "AVAILABLE";
case BookStatus::REFERENCE:
return "REFERENCE";
case BookStatus::UNAVAILABLE:
return "UNAVAILABLE";
case BookStatus::LOANED:
return "LOANED";
case BookStatus::RESERVED:
return "RESERVED";
default:
return "NONE";
}
}
std::string_view BookItem::getType() const {
switch( type ) {
case BookType::AUDIO:
return "AUDIO";
case BookType::HARDCOVER:
return "HARDCOVER";
case BookType::JOURNAL:
return "JOURNAL";
case BookType::MAGAZINE:
return "MAGAZINE";
case BookType::NEWSLETTER:
return "NEWSLETTER";
case BookType::SOFTCOPY:
return "SOFTCOPY";
default:
return "NONE";
}
}
std::ostream &operator<<( std::ostream &os, const BookItem &b ) {
os << "\nName of book: " << b.getTitle();
os << "\nAuthor of book: " << b.getAuthor();
os << "\nBook category: " << b.getCategory();
os << "\nPublication date: " << b.pubDate;
os << "\nISBN number: " << b.getIsbn();
os << "\nStatus of book: " << b.getStatus();
os << "\nType of book: " << b.getType();
return os;
}
Librarian.hh
#ifndef LIBRARIAN_HH
#define LIBRARIAN_HH
#include <iostream>
#include <string>
#include "BookItem.hh"
class System;
class Librarian {
public:
/* data-members */
std::string name;
Date dateOfHire;
/* ctors */
Librarian() = default;
Librarian( const std::string &name, const Date &dateOfHire );
// basic method-function
void printDateOfHire() const { std::cout << dateOfHire; }
/* core functionalities */
void addBook( System &sys, const BookItem &isbn );
void removeBook( System &sys, const std::string &isbn );
void auditLibrary( const System &sys ) const;
// dtor
~Librarian(){}
};
#endif
Librarian.cc
#include <iostream>
#include "../headers/System.hh"
#include "../headers/Librarian.hh"
Librarian::Librarian( const std::string &n, const Date &d ) {
name = n;
dateOfHire = d;
}
void Librarian::addBook(System &sys, const BookItem &book ) {
if ( sys.books.empty() ) {
sys.books.push_front( book );
return;
}
for ( auto bptr = sys.books.cbegin(); bptr != sys.books.cend(); ++bptr ) {
if( book.getTitle() <= bptr->getTitle() ) {
sys.books.insert(bptr, book);
return;
}
}
sys.books.push_back( book );
}
void Librarian::removeBook( System &sys, const std::string &isbn ) {
BookItem book = sys.getBook( isbn );
for ( auto bptr = sys.books.cbegin(); bptr != sys.books.cend(); ++bptr ) {
if ( book.getIsbn() == bptr->getIsbn() ) {
sys.books.remove(book);
std::cout << "Deleted { " << book.getAuthor() << " : " << book.getTitle() << " } \n";
return;
}
}
throw std::invalid_argument("Book not found");
}
void Librarian::auditLibrary( const System &sys ) const {
std::cout << "\nName of Library: " << sys.libraryName << ", Date created " << sys.dateCreated;
std::cout << "\nLibrarian: " << name << ", Date of hire: " << dateOfHire;
std::cout << "\n\nBooks: ";
for ( auto bptr = sys.books.cbegin(); bptr != sys.books.cend(); ++bptr ) {
std::cout << *bptr << "\n";
std::cout << "This book is linked to: "
<< ( ( bptr->getBookCurrentUser() == "" ) ? "None" : bptr->getBookCurrentUser() ) << "\n";
}
std::cout << "\n\nMembers: ";
for ( auto mPtr = sys.members.cbegin(); mPtr != sys.members.cend(); ++mPtr ) {
std::cout << *mPtr << "\n";
}
}
Member.hh
#ifndef MEMBER_HH
#define MEMBER_HH
#include <string>
#include <vector>
#include "Date.hh"
#include "BookItem.hh"
class System;
class Member {
friend std::ostream& operator<<( std::ostream&os, const Member &m );
private:
/* data-member */
std::string libraryNumber;
Date dateRegisted;
std::vector<BookItem> checkedOutBooks;
public:
/* data-member */
std::string name;
char sex;
/* ctors */
Member() = default;
Member( const std::string &n, const char s, Date d ) : dateRegisted( d ), name( n ), sex( s ) {}
/* method-functions */
std::string getLibraryNumber() const { return libraryNumber; }
void setLibraryNumber( const std::string &lNum ) { libraryNumber = lNum; };
void checkOut( System &sys, const std::string &isbn );
void returnBook( System &sys, const std::string &isbn );
bool operator==( const Member &m );
bool operator!=( const Member &m ) { return !( *this == m ); }
// dtor
~Member() = default;
};
#endif
System.cc
#ifndef SYSTEM_HH
#define SYSTEM_HH
#include <string>
#include <list>
#include <vector>
#include "Date.hh"
#include "BookItem.hh"
#include "Librarian.hh"
#include "Member.hh"
class System {
friend class Librarian;
friend class Member;
private:
/* data-members */
std::list<BookItem> books{};
std::vector<Member> members{};
Librarian librarian;
Member member;
public:
/* ctors */
System() = default;
System( const std::string &name, Date &date ) : libraryName( name ), dateCreated( date ) {};
/* method-functions */
const std::string generateLibraryNumber() const;
void addMember( Member &m ) { members.push_back( m ); };
void deleteMember( Member &m );
void displayMembers();
BookItem getBook( const std::string &isbn ) const;
void viewBook( const std::string isbn ) const;
void placeOnReserve( const std::string, const std::string &isbn );
void displayAllBooks() const;
/* data-members */
std::string libraryName;
Date dateCreated;
/* search functionalities */
std::list<BookItem> queryByTitle( const std::string &t ) const;
std::list<BookItem> queryByAuthor( const std::string &a ) const;
std::list<BookItem> queryByPubDate( const Date &d );
std::list<BookItem> queryByStatus( const BookStatus &s ) const;
std::list<BookItem> queryByType( const BookType &ty ) const;
// dtor
~System() = default;
};
#endif
System.cc
#include <iostream>
#include <set>
#include "../headers/System.hh"
std::list<BookItem> System::queryByTitle( const std::string &t ) const {
std::list<BookItem> queryList;
for ( auto bPtr = books.cbegin(); bPtr != books.cend(); ++bPtr ) {
if ( bPtr->getTitle().find(t) != std::string::npos )
queryList.push_back( *bPtr );
}
return queryList;
}
std::list<BookItem> System::queryByAuthor( const std::string &a ) const {
std::list<BookItem> queryList;
for ( auto bPtr = books.cbegin(); bPtr != books.cend(); ++bPtr ) {
if ( bPtr->getAuthor().find(a) != std::string::npos )
queryList.push_back( *bPtr );
}
return queryList;
}
std::list<BookItem> System::queryByPubDate( const Date &d ) {
std::list<BookItem> queryList;
for ( auto bPtr = books.begin(); bPtr != books.cend(); ++bPtr ) {
if ( bPtr->getPubDate().toString().find(d.toString()) != std::string::npos )
queryList.push_back( *bPtr );
}
return queryList;
}
std::list<BookItem> System::queryByStatus( const BookStatus &s ) const {
std::list<BookItem> queryList;
for ( auto bPtr = books.begin(); bPtr != books.cend(); ++bPtr ) {
if ( bPtr->getStatusByEnum() == s )
queryList.push_back( *bPtr );
}
return queryList;
}
std::list<BookItem> System::queryByType( const BookType &ty ) const {
std::list<BookItem> queryList;
for ( auto bPtr = books.begin(); bPtr != books.cend(); ++bPtr ) {
if ( bPtr->getTypeByEnum() == ty )
queryList.push_back( *bPtr );
}
return queryList;
}
void System::placeOnReserve( const std::string name, const std::string &isbn ) {
for ( auto bPtr = books.begin(); bPtr != books.end(); ++bPtr ) {
if ( bPtr->getIsbn() == isbn ) {
bPtr->setStatus( BookStatus::RESERVED );
bPtr->setBookCurrentUser( name );
}
}
}
BookItem System::getBook( const std::string &isbn ) const {
for ( auto bPtr = books.cbegin(); bPtr != books.cend(); ++bPtr ) {
if ( bPtr->getIsbn() == isbn )
return *bPtr;
}
throw std::invalid_argument("Book is not available at the library");
}
void System::viewBook( const std::string isbn ) const {
std::cout << getBook( isbn );
}
const std::string System::generateLibraryNumber() const {
static std::string Codes[10]{"XGS", "QWT", "OPI", "NMK", "DXF", "PXG", "OPI", "QPU", "IKL", "XYX" };
static std::set< unsigned, std::greater<unsigned> > idSet;
unsigned id;
bool unique = false;
unsigned index = 0 + rand() % 9;
std::string code = Codes[ index ];
while ( unique == false ) {
id = 10000000 + rand() % 9999999;
auto ret = idSet.emplace(id);
if ( !ret.second ) {
std::cout << "unique failed";
unique = false;
continue;
}
else
unique = true;
}
return code + std::to_string( id );
}
void System::deleteMember( Member &m ) {
for ( auto mPtr = members.begin(); mPtr != members.end(); ++mPtr ) {
if ( *mPtr == m ) {
members.erase( mPtr );
std::cout << "Deleted member: { Name: " << m.name << ", Library Number: " << m.getLibraryNumber() <<
" }\n";
return;
}
}
throw std::invalid_argument("No such member found");
}
void System::displayMembers() {
std::cout << "Members of Library: ( count : " << members.size() << " ) " << "\n";
for ( auto mPtr = members.cbegin(); mPtr != members.cend(); ++mPtr ) {
std::cout << *mPtr;
}
}
void System::displayAllBooks() const {
for ( auto bPtr = books.begin(); bPtr != books.end(); ++bPtr ) {
std::cout << *bPtr << "\n\n";
}
}
```
Risposte
Date:
Date.hhmancano alcuni include (<iostream>,<string>).Non fornire un costruttore predefinito. Non ha senso avere una data predefinita.
Non fornire costruttori a uno e due argomenti. È improbabile che specificare un mese e una data nel 1970 sia molto comune.
Dovremmo sostenere anni prima del 1970. C'erano libri allora!
yeardovrebbe essere un numero con segno (anche se è improbabile che tale capacità venga utilizzata).dayemonthpossono essere tipi più piccoli (ad esempiostd::uint8_t).setDate()non è necessario, poiché abbiamo il costruttore e l'assegnazione.Ci si aspetterebbe che una funzione chiamata
nextDay()restituisca una copia, piuttosto che modificare l'Dateistanza (cf iteratori della libreria standardnexteadvance).Se il distruttore non fa nulla, possiamo ometterlo.
validateDatenon può mai tornarefalse, quindi dovrebbe avere unvoidvalore di ritorno (e forse essere chiamatothrowIfInvalido qualcosa di simile).È possibile creare funzioni membro che non richiedono l'accesso alle variabili membro di un'istanza di classe (
validateDateecc.)static.Suggerirei di stampare le date come "aaaa-mm-gg" (o stampare il mese per nome). Mettere la giornata al centro è altamente illogico.
Se hai C ++ 20, usa
std::chrono::year_month_dayinvece!
BookItem:
Dovremmo usare l'elenco degli inizializzatori del costruttore per inizializzare le variabili membro.
Di nuovo, non vogliamo un costruttore predefinito.
Non è necessario specificare un distruttore.
Tieni presente che le biblioteche hanno spesso diverse copie dello stesso libro. Quando un libro ha un ISBN (pubblicazioni tradizionali dopo il 1970), non è necessario duplicare i dati del libro (titolo, autore, ecc.) Per ogni copia del libro nella biblioteca. Forse dovremmo spostare i dati del libro in una classe separata, e hanno un
std::variant<ISBN, BookData>inBookItem? (Ma forse è troppo per questa implementazione).Dovremmo aggiungere un identificatore univoco per ogni elemento contenuto nella libreria.
Librarian:
addBookeremoveBooknon dovrebbe far parte di questa classe. Modificano gliSysteminterni della classe e dovrebbero far parte dellaSystemclasse.auditLibrarydovrebbe essere spostato anche lì.Il costruttore predefinito non dovrebbe esistere. Il distruttore non ha bisogno di esistere.
Non penso che questa classe debba esistere affatto per la funzionalità della libreria corrente.
Member:
Costruttore predefinito non valido. Distruttore non necessario.
Non vogliamo davvero memorizzare
BookItemi messaggi qui in base al valore. Dobbiamo solo memorizzare un ID per ogni articolo che hanno estratto.checkOutereturnBooknon dovrebbero essere qui, dovrebbero far parte diSystem.
System:
Non dovrebbe avere
friends.Forse dovrebbe essere chiamato
Library.Non preoccuparti della velocità a meno che non diventi effettivamente un problema. Probabilmente non ha senso nemmeno memorizzare i libri per titolo (è probabile che desideriamo cercare per autore, categoria o data di pubblicazione o ...).
(Nota che la ricerca del titolo non tiene conto del fatto che l'elenco è ordinato per titolo!)
std::listha pochi usi validi. Diventa solo più velocestd::vectordell'inserimento e della rimozione di molti (centinaia di migliaia) di elementi dal centro dell'elenco.std::vectorandrebbe bene qui.Usa gamma-based per i loop, se del caso:
for (auto const& i: books) { ... }.Notare che l'
<algorithm>intestazione fornisce varie funzioni per trovare e copiare cose.
Ho letto la risposta di @ user673679 e desidero solo risolvere alcuni problemi.
Sono fortemente in disaccordo con la disabilitazione dei costruttori predefiniti in classi come Member/Date/BookItem. Se la classe non ha un costruttore di default quindi utilizzarlo con std::vector, std::mape altri contenitori di template diventa molto scomodo in generale. Quindi è un cattivo consiglio.
Invece dovresti rendere le classi costruite di default come queste chiaramente non inizializzate e aggiungere funzioni / metodi che lo testano.
Un'altra nota: Voglio espandere su std::vectorvs std::list. std::listè un contenitore molto lento e inadatto alla maggior parte degli scopi. Richiede una nuova allocazione per ogni elemento e per trovare un elemento intermedio è necessario attraversare metà della lista. Quindi usare std::listè quasi un sacrilegio. Ci sono alcuni rari casi in cui l'elenco potrebbe essere utile, ma sicuramente non in questo codice.
Usa std::vectorinvece. Alla fine dovrai capire come gestire correttamente la memoria e la ricerca per la ricerca ma usarla std::vectorper memorizzare le classi di base. Ad esempio, cancellare un singolo elemento non vale la pena riorganizzare l'intero vettore. Invece, tieni semplicemente il conteggio del numero di posizioni vuote e se il numero supera la metà delle dimensioni totali, riorganizzalo.
Ti manca ancora il costruttore di mosse e l'assegnazione di mosse per BookItem. Averlo migliorerà le prestazioni di classi come std::vectorquella che contengono gli elementi del libro.