Movie Store in C++

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