MovieMan – adding menus

It's time now to take some of the features covered in this chapter and apply them to MovieMan. The code listings here will, unfortunately, be incomplete in the interests of keeping the page count down. However, the complete implementation can be found in the Chapter03/MovieMan directory of the downloadable source code.

The MovieMan menus are implemented as a small set of classes: Menu, MainMenu, and DisplayMenu. We could debate composition versus inheritance, or the use of classes to implement such simple menus, but my priority is to provide a demonstration of D features.

The Menu base class

To get started, save a file called $MOVIEMAN/source/movieman/menu/menu.d. At the very top, add the following lines:

module movieman.menu.menu;
import std.stdio;
import movieman.io;

Next, we'll make an empty class declaration that we'll fill in a little at a time:

class Menu {
}

The Menu class will have three private members:

private:
    string _header;
    string[] _items;
    bool _shouldExit;

_header is the title of the menu and _items is the text specifying the available actions, each of which will be displayed in a numbered list for the user to input a selection. _shouldExit is a flag that will be set when a subclass is ready to exit the menu loop. Now we've got a few protected functions, starting with the constructor:

protected:
    this(string header, string[] items) {
        _header = header;
        _items = items;
    }

A protected constructor is normally used to allow subclasses access while preventing instances of the class from being instantiated outside the class scope. That's the exact behavior we want for Menu, but it isn't necessary to make the constructor protected in this case as the very next function is abstract; I simply prefer to make all constructors in an abstract class protected:

abstract void handleSelection(uint selection);

Subclasses will override handleSelection in order to respond to the user's selection of a menu item. The next function, exitMenu, is called when a subclass is ready to give up control and terminate the loop that prints the menu and reads the user's selection:

void exitMenu() { _shouldExit = true; }

The next two functions ensure that a title or number entered by the user is valid and print an "abort" message if not. The former only checks whether title is null for now and the latter if the number is 0. The label parameter is used in the error message:

bool validateTitle(string title) {…}
bool validateNumber(uint number, string label) {…}

The last function in Menu is the one that drives things:

public:
  final void run() {
    do {
      printList(_header, _items);
      auto selection = readUint();
        if(!selection || selection > _items.length)
          writefln("Sorry, that's an invalid selection. Please try again.");
        else
          handleSelection(selection);

    } while(!_shouldExit);
  }
}

The public: keyword before the function declaration "turns off" the protected: added previously. The final in the declaration prevents subclasses from overriding run. The implementation is a loop that begins with a call to movieman.io.printList to display the header and the numbered menu items. It then asks the user to enter a number, which it sends to the subclass to handle if it is in the range of valid options (which is 1 to items.length, inclusive), then the loop goes back up to the top and the menu is displayed again, unless exitMenu is called.

The MainMenu class

Save a new file as $MOVIEMAN/source/movieman/menu/main.d. Add the following lines to the top:

module movieman.menu.main;
import movieman.menu.menu;
final class MainMenu : Menu {
}

Note that the class is declared final. Given that there's no need to ever subclass MainMenu, we can use this not just as a preventative measure, but also as an optimization hint. For example, the compiler can be sure that none of the member functions will ever be overridden and can safely inline calls to them. Now, inside MainMenu:

private:
enum Options : uint {
  addMovie = 1u,
  displayMovie,
  exit,
}
Menu _displayMenu;

The Options enumeration will be used to determine the action to take in handleSelection. The user can choose to add a new movie, display one or more movies, or exit the program. The _displayMenu member is an instance of DisplayMenu, declared as an instance of the Menu base class. It is created in one of two private functions, onDisplayMovie, when the user chooses to display a movie:

void onDisplayMovie() {
  import movieman.menu.display : DisplayMenu;
  if(_displayMenu is null)
    _displayMenu = new DisplayMenu;
    _displayMenu.run();
}

A local selective import is used since DisplayMenu is not used anywhere else in the module. _displayMenu is allocated only if it's null, then its run function is called. The second private function adds a movie to the database:

void onAddMovie() {
  import movieman.io;
  import std.stdio : writeln;
  auto title = readTitle();
  if(!validateTitle(title))
    return;
  auto caseNumber = readNumber("case");
  if(!validateNumber(caseNumber, "case"))
    return;
  auto pageNumber = readNumber("page");
  if(!validateNumber(pageNumber, "page"))
    return;
  if(readChoice("add this movie to the database"))
    writeln("Adding movie!");
  else
    writeln("
Discarding new movie.");
}

This function asks the user to enter a title, case number, and page number. If any one of these is invalid, the function returns without making any changes. Finally, it gives the user a chance to verify the information is correct via readChoice. If the user approves, a message is printed saying the movie has been added. Later in the book, this will be changed to add a movie to the database. Next:

protected:
  override void handleSelection(uint selection) {
    final switch(cast(Options)selection) with(Options) {
      case addMovie:
        onAddMovie();
        break;
      case displayMovie:
        onDisplayMovie();
        break;
      case exit:
        exitMenu(); 
        break;
    }
  }

This function uses a final switch to cover every member of the Options enumeration. with(Options) is used as a convenience to avoid adding the namespace in every case. Notice that there's no private function to handle the exit option. Instead, the exitMenu function implemented in the base class is called. Finally, the public constructor:

public:
  this() {
    auto options = [
      "Add Movie",
      "Display Movie(s)",
      "Exit"
    ];
    super("Select one of the following actions.", options);
  }
}

When a class has no default constructor, either implicit or explicit, then any subclasses must call one of the constructors that have been implemented. The MainMenu class must call Menu's sole constructor. Before doing so, an array of menu items is allocated as options with an array literal. Using the literal directly in the constructor call would have been somewhat less readable. Finally, the Menu constructor is called via super.

The DisplayMenu class

This DisplayMenu class will eventually print movie data to the screen and provide the option to edit movies in the database. The implementation is similar to the MainMenu class, though the only function currently implemented is handleSelection, which does the work of reading input and printing responses (there's no movie data yet to display or edit). Before looking at the implementation in the downloadable source, consider how you might implement DisplayMenu, using MainMenu as a guide.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.80.100