Contents

Overview
The learning objectives of this project were:
- Factory pattern utilization
- Memory management
- Queue commands for later execution
The project was to create store-like structure that could read data from file, store it, and modify it with commands read from file. Below is a basic diagram of the program.

Input
Customers and inventory are populated from text files. Using a fileread class, I queued each line from the text file and then executed each command. I set the program up this way to be modular with the command functions of the program. Commands are in the form of a text file as well and are read into a queue. Commands were made using a factory pattern so that new types of commands could be easily added to the program.
Example of customers.txt
3333 Witch Wicked
8888 Pig Porky
4444 Moose Bullwinkle
9999 Duck Daffy
Example of movies.txt
C, 10, Victor Fleming, The Wizard of Oz, Judy Garland 7 1939
F, 10, Nora Ephron, Sleepless in Seattle, 1993
D, 10, Barry Levinson, Good Morning Vietnam, 1988
D, 10, Barry Levinson, Same Director Good Morning Vietnam, 1988
Example of the commands.txt
B 8000 D F You've Got Mail, 1998
B 1000 D D Barry Levinson, Good Morning Vietnam,
B 1000 D C 5 1940 Katherine Hepburn
B 2000 D F Sleepless in Seattle, 1993
moviestore.cpp
The main brain of the program that handle the following:
- handles store population
- handles customers
- store inventory
- execute commands
class MovieStore {
public:
// default constructor
MovieStore() = default;
// destructor
~MovieStore();
// add a single customer
bool addCustomer(const std::string &CustomerInput);
// add multiple customers
bool addCustomers(const std::string &FileInput);
// stock inventory from file
bool stockInventory(const std::string &FileInput);
// read commands from file
void readCommands(const std::string &FileInput);
// execute stored commands
void executeCommands();
// moviestore functions
void mapInventory();
private:
// customer container
std::map<int, Customer*> Customers;
// private variables
std::queue<std::string> CustomersToBeAdded;
Inventory Stock;
FileRead CommandFileInput;
};
fileread.cpp
The fileread class was used to parse a text file into readable lines for the program. It is passed an input string and returns a standard queue of strings that represent each line of the text.
class FileRead {
public:
// default constructor
FileRead() = default;
// default destructor
~FileRead() = default;
// reads txt file and converts to strings
void readFromFile(const std::string &FileInput);
// clears the container
void clear();
// container of strings
std::queue<std::string> ReadLines;
};
command.cpp
Commands are what the program uses for interaction. If the program wants to add, remove, borrow, or return a movie or customer, it is done through a command. I designed the program to function like this so that commands could be queue or have delayed execution.
Commands are registered types and only registered commands are recognized by the program. This was done with a modular design in mind so new commands could be added if wanted.
class CommandFactory {
public:
virtual ~CommandFactory() = default;
virtual Command *create(const std::string &CommandInput) const = 0;
};
// abstract command class
class Command {
public:
// destructor
virtual ~Command() = default;
// register new command
static void registerType(char Case, CommandFactory* CF);
// abstract create command
static Command *create(char Case, const std::string &CommandInput);
// abstract execute command
virtual void execute(Customer *Cust, Inventory *Stock) = 0;
// return command customer ID
int getCustomerID();
// container
static std::map<char, CommandFactory*> CommandFactories;
protected:
// protected variables
char Genre = ' ';
int CustomerID = 0;
std::string Title;
std::string ReleaseYear;
std::string Director;
std::string MajorActor;
};
The moviestore executes valid commands when the execute function is called.
// execute commands
void MovieStore::executeCommands() {
// debug
std::cout << "Executing Commands....\n";
Command *C;
while (!CommandFileInput.ReadLines.empty()) {
// test for command function
std::string Front = CommandFileInput.ReadLines.front();
char Case = Front.at(0);
C = Command::create(Case, Front);
// check if nullptr
if (C == nullptr) {
// bad command if nullptr
// debug
std::cout << "Bad command...\n";
CommandFileInput.ReadLines.pop();
continue;
}
C->execute(Customers[C->getCustomerID()], &Stock);
delete C;
CommandFileInput.ReadLines.pop();
}
}
customer.cpp
Customers were store in a standard map using an integer as the key and a customer class as the value. The customer class store information about the customer, including a history of their transactions.
class Customer {
friend std::ostream &operator<<(std::ostream &Os, const Customer &C);
public:
// default constructor
Customer() = delete;
// destructor
~Customer();
// constructor ID#, FName, LName
Customer(int IDNumber, const std::string &FName, const std::string &LName);
// no name customer
explicit Customer(int IDNumber);
// return customer ID#
int getID() const;
// return name <FName, LName>
std::pair<std::string, std::string> getName() const;
// record Tx
void recordTransaction(char Case, Movie *M);
// display last Tx
void displayLastTransaction() const;
// display all Tx
void displayAllTransactions() const;
// display all rentals
void displayAllRentals() const;
// display all returns
void displayAllreturn() const;
private:
// private variables
int CustomerID = -1;
std::pair<std::string, std::string> CustomerName;
History *CustomerTransactionHistroy;
};
There is nothing special about this class, the functionality is pretty straight forward.
history.cpp
The transaction history of a customer is store here. Transactions are store in vectors based on type, returns or rentals. I split them for a few reason. The first, I wanted to be able to display or filter just returns or rentals. Second, I needed a way to check for outstanding rentals when inventorying the store class. To do so all I needed to do was compare rentals with returns.
class History {
public:
// default constructor
History() = default;
// default destructor
~History() = default;
// records a transaction
void recordTransaction(char Case, Movie *M);
// clear histories
void clearHistory();
// displays last transaction
void displayLastTransaction() const;
// displays all transactions
void displayAllTransactions() const;
// displays returns
void displayReturns() const;
// displays rentals
void displayRentals() const;
private:
// container for rentals
std::vector<Transaction*> RentalHistory;
// container for returns
std::vector<Transaction*> ReturnHistory;
// last transaction for account
Transaction *LastTransaction = nullptr;
};
transaction.cpp
A straight forward class: takes in a string and stores it. I overloaded the ostream to display the string.
class Transaction {
friend std::ostream &operator<<(std::ostream &Os, const Transaction &T);
public:
// no default constructor
Transaction() = delete;
// constructor
explicit Transaction(Movie *M);
// default destructor
~Transaction() = default;
private:
// variables
std::string TransX;
};
inventory.cpp
The inventory class is the container for movies. It stores movies in a custom hash map. The store can add, borrow, or return a movie. Borrow and return have a return type of a standard pair of a movie pointer and bool so that it can tell if it was successful and get the movie in the same call.
class Inventory {
public:
// default constructor
Inventory() = default;
// default destructor
~Inventory() = default;
// display inventory
void displayMap();
// add a movie to inventory
bool addMovie(char Case, const std::string &FileInput);
// borrow a movie from the store
std::pair<Movie*, bool> borrowMovie(char Genre, const std::string &Key);
// return borrowed movie from store
std::pair<Movie*, bool> returnMovie(char Genre, const std::string &Key);
private:
// map that stores movies with genre as key
HashMap MovieHashMap;
};
hashmap.cpp
Part of the project was to create a custom hash map container. I chose to store movies in mine in order to ensure only single copies of movies were added to the inventory. The hash map is simple and straight forward.
class HashMap {
public:
// default constructor
HashMap() = default;
// destructor
~HashMap();
// add element
bool addElement(char Genre, Movie *M);
// find element
std::pair<Movie*, bool> find(char Genre, const std::string &Key);
// print values in map
void printHash(int Index);
private:
// container
std::array<std::map<std::string, Movie*>, 26> MapElements;
};
The hash map is a basic bucket array with the genre of the movie as the bucket. The key is a combination of strings determined by the genre of movie.
Drama: Director + Title
Comedy: Title + Release Year
Classic: Release Year + Major Actor
bool HashMap::addElement(char Genre, Movie *M) {
// zero out indicies
int Index = Genre - 'A';
// is it a valid char?
if (Index <= 0 || Index >= 25) {
std::cout << "Invalid index...\n";
return false;
}
// push into map based on genre
MapElements[Index].emplace(M->getKey(), M);
// sort list
return true;
}
movie.cpp
I used a factory pattern to create movies. Each genre has a registered type and can be done so in a modular way from the store. I chose to implement the process this was so that I can immediately check the genre when creating a new movie. If it is not a registered type, the call will terminate.
auto *FF = new ComedyFactory();
Movie::registerType('F', FF);
auto *CF = new ClassicFactory();
Movie::registerType('C', CF);
auto *DF = new DramaFactory();
Movie::registerType('D', DF);
Each movie has common attributes and some unique to the genre. Most functions are virtual.
// abstract movie factory
class MovieFactory {
public:
// abstract create
virtual ~MovieFactory() = default;
virtual Movie *create(const std::string& MovieInput) const = 0;
private:
};
// abstract movie class
class Movie {
// outstream overload
friend std::ostream &operator<<(std::ostream &Os, const Movie &M);
public:
// abstract create
static Movie *create(char Genre, const std::string& MovieInput);
// default destructor
virtual ~Movie() = default;
//register a movie factory for expansion
static void registerType(char Genre, MovieFactory* MF);
// greater than overload
virtual bool operator>(const Movie &M) const = 0;
// container for factories
static std::map<char, MovieFactory*> Moviefactories;
// return title
std::string const& getTitle() const;
// return director
std::string const& getDirector() const;
// return release year
std::string const& getReleaseYear() const;
// return major actor
std::string const& getMajorActor() const;
// return key
std::string const& getKey() const;
// return stock
int const& getStock() const;
// check movie out
void checkout();
// check movie in
void checkin();
protected:
// protected variables of movie
int Stock = 10;
std::string Title;
std::string YearRelease;
std::string Director;
std::string MajorActor;
std::string Key;
};
Movie Types
For this project, I had three movie genres. However, the program was implemented so that new genres could be easily added.
class Drama : public Movie {
public:
// constructor
Drama(const std::string &Director, const std::string &Title,const std::string &ReleaseYear);
// greater than overload
bool operator>(const Movie& M) const override;
};
class DramaFactory : public MovieFactory {
public:
// create override
Movie* create(const std::string &MovieInput) const override;
};
Project Links
You can access the repository of this project here:
https://github.com/uwbclass/uwb2020wi343b-movies-Scottin3d