Object Oriented Library Management System

Oct 18 2020

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

4 user673679 Oct 19 2020 at 17:05

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!

  • year dovrebbe essere un numero con segno (anche se è improbabile che tale capacità venga utilizzata).

  • daye monthpossono essere tipi più piccoli (ad esempio std::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 standard nexte advance).

  • Se il distruttore non fa nulla, possiamo ometterlo.

  • validateDatenon può mai tornare false, quindi dovrebbe avere un voidvalore di ritorno (e forse essere chiamato throwIfInvalido 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>in BookItem? (Ma forse è troppo per questa implementazione).

  • Dovremmo aggiungere un identificatore univoco per ogni elemento contenuto nella libreria.


Librarian:

  • addBooke removeBooknon dovrebbe far parte di questa classe. Modificano gli Systeminterni della classe e dovrebbero far parte della Systemclasse. 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.

  • checkOute returnBooknon dovrebbero essere qui, dovrebbero far parte di System.


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ù veloce std::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.

2 ALX23z Oct 19 2020 at 18:13

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.