Système de gestion de bibliothèque orienté objet
Le système de gestion de bibliothèque est un programme orienté objet qui s'occupe de la gestion de base d'une bibliothèque. Ceci est la troisième partie d'une série. La première itération du projet se trouve ici et la deuxième itération se trouve également ici
Les principaux acteurs sont le bibliothécaire, le membre et le système.
Préoccupation majeure
Je me suis fait des amis membres et bibliothécaires de la classe système et j'ai également composé la classe système avec bibliothécaire et membre. J'ai senti que «Le système est composé d'un bibliothécaire et de membres» sonne mieux que «Le système est un ami d'un bibliothécaire et de membres». Est-ce que cela suit les modèles de conception courants?
Dans mon article précédent, je n'étais pas assez clair sur la raison pour laquelle un std :: vector devrait être préférable à std :: list dans cette situation en raison du fait que le système effectuait fréquemment l'insertion. Est-ce que std :: vector serait plus évolutif et polyvalent tout en tenant compte de la vitesse et de l'efficacité?
Toute autre observation sur les écueils potentiels, les pièges et les mauvaises pratiques courantes peut être signalée.
Date.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";
}
}
```
Réponses
Date:
Date.hhil manque quelques inclusions (<iostream>,<string>).Ne fournissez pas de constructeur par défaut. Cela n'a pas de sens d'avoir une date par défaut.
Ne fournissez pas de constructeurs à un et deux arguments. Il est peu probable que la spécification d'un mois et d'une date en 1970 soit très courante.
Nous devrions soutenir des années avant 1970. Il y avait des livres à l'époque!
yeardoit être un nombre signé (même s'il est peu probable que cette capacité soit utilisée).dayetmonthpeuvent être de plus petits types (par exemplestd::uint8_t).setDate()n'est pas nécessaire, car nous avons le constructeur et l'affectation.On s'attendrait à ce qu'une fonction appelée
nextDay()renvoie une copie, plutôt que de modifier l'Dateinstance (cf itérateurs de bibliothèque standardnextetadvance).Si le destructeur ne fait rien, nous pouvons l'omettre.
validateDatene peut jamais retournerfalse, donc il devrait avoir unevoidvaleur de retour (et peut-être être appeléthrowIfInvalidou quelque chose de similaire).Les fonctions membres qui n'ont pas besoin d'accéder aux variables membres d'une instance de classe (
validateDateetc.) peuvent être crééesstatic.Je suggérerais d'imprimer les dates comme "aaaa-mm-jj" (ou d'imprimer le mois par son nom). Mettre le jour au milieu est très illogique.
Si vous avez C ++ 20, utilisez à la
std::chrono::year_month_dayplace!
BookItem:
Nous devrions utiliser la liste d'initialisation du constructeur pour initialiser les variables membres.
Encore une fois, nous ne voulons pas d'un constructeur par défaut.
Nous n'avons pas besoin de spécifier un destructeur.
Notez que les bibliothèques ont souvent plusieurs exemplaires du même livre. Lorsqu'un livre a un ISBN (publications grand public après 1970), nous n'avons pas besoin de dupliquer les données du livre (titre, auteur, etc.) pour chaque exemplaire du livre dans la bibliothèque. Peut-être que nous devrions déplacer les données de livre dans une catégorie distincte, et un
std::variant<ISBN, BookData>enBookItem? (Mais peut-être que cela va trop loin pour cette implémentation).Nous devrions ajouter un identifiant unique pour chaque élément de la bibliothèque.
Librarian:
addBooketremoveBookne devrait pas faire partie de cette classe. Ils modifient les élémentsSysteminternes de la classe et devraient faire partie de laSystemclasse.auditLibrarydevrait être déplacé là aussi.Le constructeur par défaut ne devrait pas exister. Le destructeur n'a pas besoin d'exister.
Je ne pense pas que cette classe ait besoin d'exister du tout pour la fonctionnalité actuelle de la bibliothèque.
Member:
Le constructeur par défaut est mauvais. Destructeur inutile.
Nous ne voulons pas vraiment stocker les
BookItems ici par valeur. Nous avons juste besoin de stocker un identifiant pour chaque article qu'ils ont extrait.checkOutetreturnBookne devraient pas être ici, ils devraient en faire partieSystem.
System:
Ne devrait pas avoir l'
friendart.Devrait peut-être être appelé
Library.Ne vous inquiétez pas de la vitesse à moins que cela ne devienne un problème. Il est probablement inutile de stocker même les livres par titre (nous sommes aussi susceptibles de vouloir rechercher par auteur ou par catégorie ou par date de publication ou ...).
(Notez que la recherche par titre ne prend pas en compte le fait que la liste est triée par titre!)
std::lista peu d'utilisations valables. Cela devient seulement plus rapide questd::vectorpour insérer et supprimer plusieurs (centaines de milliers) d'éléments du milieu de la liste.std::vectorserait bien ici.Utilisez gamme basée pour les boucles , le cas échéant:
for (auto const& i: books) { ... }.Notez que l'en-
<algorithm>tête fournit diverses fonctions pour rechercher et copier des choses.
J'ai lu la réponse de @ user673679 et je veux juste résoudre quelques problèmes.
Je ne suis pas du tout d'accord avec la désactivation des constructeurs par défaut dans des classes comme Member/Date/BookItem. Si la classe n'a pas de constructeur par défaut, l'utiliser avec std::vector, std::mapet d'autres conteneurs de modèles devient très gênant en général. C'est donc un mauvais conseil.
Au lieu de cela, vous devez faire en sorte que les classes construites par défaut telles que celles-ci soient clairement non initialisées et ajouter des fonctions / méthodes qui les testent.
Une autre note: Je veux élargir std::vectorvs std::list. std::listest un conteneur très lent et inadapté à la plupart des usages. Cela nécessite une nouvelle allocation pour chaque élément et pour trouver un élément du milieu, il faut parcourir la moitié de la liste. Donc utiliser std::listest presque un sacrilège. Il y a quelques rares cas où la liste pourrait être bénéfique mais certainement pas dans ce code.
Utilisez std::vectorplutôt. Vous devrez éventuellement comprendre comment gérer correctement la mémoire et la recherche pour la recherche, mais l'utiliser std::vectorpour stocker les classes de base. Par exemple, effacer un seul élément ne vaut pas la peine de réorganiser tout le vecteur. Au lieu de cela, gardez simplement le compte du nombre d'emplacements vides et si le nombre dépasse la moitié de la taille totale, réorganisez-le.
Vous n'avez toujours pas de constructeur de déplacement ni d'affectation de déplacement pour BookItem. L'avoir améliorera les performances des classes comme celles std::vectorqui contiennent les éléments du livre.