Menu class. Need to add "Go back to previous menu"

Here is a simplified version of my Menu class, where submenus can be inserted arbitrarily deep. I need to add a new functionality "go back to previous menu", which I would like to be activated by entering 0 (universal command for all Menu instances). I considered the Memento Pattern, but that doesn't seem to quite fit. Could someone help me add that functionality to my Menu class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#include <iostream>
#include <cstring>

using namespace std; 
const int END = -1,  NO_SUBMENU = 0;

class Menu {
	public:
		Menu () {first = nullptr;  id = lastId++;}
		~Menu ();
		void Insert (const char *foodName, const double cost, const Menu* submenu = nullptr, const int pos = END);  
		void Delete (const int pos = END);
		int Print () const;
		int Choose () const;
		int ID () {return id;}
	private:
		class Option {
			public:
				Option (const char*, const double, const Menu* = nullptr);
				~Option ();
				const char* Name () {return name;}
				double Price () {return price;}
				const Menu* Submenu ()  {return submenu;}
				Option*& Next () {return next;}
				void Print () const;
				int Choose () const;
			private:
				char *name;
				double price;
				const Menu *submenu;
				Option *next;
		};
		Option *first;  // first option in the menu
		int id;
		static int lastId;
};

Menu::Option::Option (const char* foodName, const double cost, const Menu* menu): submenu (menu)
{  
	name = new char [strlen (foodName) + 1];
	strcpy (name, foodName);
	price = cost;
	next = nullptr;
}

Menu::Option::~Option () {
	delete name;
}

void Menu::Option::Print () const {
	cout << name;
	if (submenu == nullptr)
		cout << "....." << price;
	else
		cout << " -> (submenu)";
	cout << endl;
}

int Menu::Option::Choose () const {
	if (submenu == nullptr)
		return NO_SUBMENU;
	else
	{
		cout << endl << "Submenu entered." << endl;
		return submenu->Choose ();
	}
}

int Menu::lastId = 0;

Menu::~Menu ()
{
	Menu::Option *current, *next;
	for (current = first; current != nullptr; current = next)
	{
		next = current->Next ();
		delete current;
	}
}

void Menu::Insert (const char *str, const double cost, const Menu* submenu, const int pos) {
	Menu::Option *newOption = new Menu::Option (str, cost, submenu);
	Menu::Option *current, *prev = nullptr;
	int idx = 0;
	for (current = first; current != nullptr && idx++ != pos; current = current->Next ())
		prev = current;
	if (prev == nullptr) 
	{ 
		newOption->Next () = first;
		first = newOption;
	} 
	else
	{ 
		newOption->Next () = current;
		prev->Next () = newOption;
	} 
}

void Menu::Delete (const int pos) {
	Menu::Option *current, *prev = nullptr;
	int idx = 0;
	for (current = first; current != nullptr && current->Next () != nullptr && idx++ != pos; current = current->Next())
		prev = current;
	if (current != nullptr) 
	{
		if (prev == nullptr)
			first = current->Next ();
		else
			prev->Next () = current->Next ();
		delete current;
	}  
}

int Menu::Print () const {
	int n = 0;
	Menu::Option *current = first;
	for (current = first; current != nullptr; current = current->Next ())
	{
		cout << ++n << ". ";	
		current->Print ();
	}
	return n;
}

int Menu::Choose () const {
	int n, choice;
	Menu::Option *current = first;
	do {
		cout << "Please choose an option:" << endl;
		n = this->Print ();
		cout << "Option? ";
		cin >> choice;
	} while (choice <= 0 || choice > n);
	n = 1;
	for (current = first; n != choice && current != nullptr; current = current->Next ())
		++n;
	n = current->Choose ();
	return (n == NO_SUBMENU ? choice : n);
}

int main () {
	Menu menu;
	menu.Insert ("Hamburger", 3.49);
	menu.Insert ("Fries", 1.89);
	menu.Insert ("Taco", 0.99);
	menu.Insert ("Coke", 1.49, nullptr, 1);
	
	Menu wineMenu;
	wineMenu.Insert ("Red wine", 29.99);
	wineMenu.Insert ("White wine", 39.99);
	
	menu.Insert ("Wine", 0, &wineMenu);
	
	Menu vintageWineMenu;
	vintageWineMenu.Insert ("Chateau Talbot", 450.00);
	vintageWineMenu.Insert ("Domaine de la Romanee", 1500.00);
	vintageWineMenu.Insert ("Penfolds Grange Bin 95", 2700.00);
	
	wineMenu.Insert ("Vintage Wine", 0, &vintageWineMenu, 0);  // submenu within submenu (there is no limit)
	
	int menuChoice = menu.Choose ();
	cout << endl << "You chose option " << menuChoice << "." << endl;
}

Last edited on
Here is my motivation. After this sample run:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Please choose an option:
1. Hamburger.....3.49
2. Coke.....1.49
3. Fries.....1.89
4. Taco.....0.99
5. Wine -> (submenu)
Option? 5

Submenu entered.
Please choose an option:
1. Vintage Wine -> (submenu)
2. Red wine.....29.99
3. White wine.....39.99
Option? 1

Submenu entered.
Please choose an option:
1. Chateau Talbot.....450
2. Domaine de la Romanee.....1500
3. Penfolds Grange Bin 95.....2700
Option?

The chooser may change his mind about ordering vintage wine after seeing the vintage wine options (or the crazy prices), and then decide to simply order a hamburger. Or perhaps the entire menu has many, many submenus and he wants to look through every submenu before making his final order. But this is only possible if he is able to go back up one menu (by entering 0).

Perhaps my entire Menu class needs to be redesigned to add the "go back to previous menu" functionality?
Last edited on
Composite with back-reference to the parent in the recursive structure?
https://en.wikipedia.org/wiki/Composite_pattern

Something along these lines, perhaps:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
struct menu_element
{
    menu_element() = default ;
    explicit menu_element( menu_element* parent ) : parent_(parent) {}

    virtual ~menu_element() = default ;

    virtual std::ostream& print( std::ostream& stm ) const = 0 ;
    virtual const menu_element* parent() const { return parent_ ; }

    virtual void push( menu_element* ) { throw "not implemented" ; }

    // ....

     menu_element* parent_ = nullptr ;
};

struct menu_item : menu_element
{
    menu_item( std::string text, std::size_t id, menu_element* parent ) 
        : menu_element(parent), id(id), descr(text) {}

    virtual std::ostream& print( std::ostream& stm ) const override
    { return stm << id << ". " << descr << '\n' ; }

    // ...

    std::size_t id ;
    std::string descr ;

};

struct submenu : menu_element
{
    submenu( std::string text, menu_element* parent ) : menu_element(parent), descr(text) {}

    virtual std::ostream& print( std::ostream& stm ) const override
    { return stm << descr << " (submenu)\n" ; }

    virtual void push( menu_element* m ) override { m->parent_ = this ; contents.push_back(m) ; }

    // ...


    std::string descr ;

    std::vector<menu_element*> contents { std::addressof(back_to_parent) } ;

    menu_item back_to_parent { "back", 0, this } ;
};
Thanks JLBorges, for the idea. I was searching so hard for a design pattern when the solution was simply to make the linked list partially doubly linked. Here is the full solution in case anyone needs a solution to a similar problem:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include <iostream>
#include <cstring>
#include <vector>

using namespace std; 
const int END = -1,  NO_SUBMENU = 0,  GO_BACK = 0;

class Menu {
	public:
		Menu() {id = lastId++;}
		~Menu();
		void Insert (const char *foodName, const double cost, Menu* submenu = nullptr, const int pos = END);  
		void Delete (const int pos = END);
		int Print() const;
		int Choose() const;
		int ID() {return id;}
	private:
		class Option {
			public:
				Option (const char*, const double, const Menu* = nullptr);
				~Option();
				const char* Name() {return name;}
				double Price() {return price;}
				const Menu* Submenu()  {return submenu;}
				Option*& Next() {return next;}
				void Print() const;
				int Choose() const;
			private:
				char *name;
				double price;
				const Menu *submenu;
				Option *next;
		};
		Option* first = nullptr;
		Menu* parent = nullptr;  // *** Added
		std::vector<Menu*> submenus;  // *** Added (not actually needed in this simple model, but might be useful later)
		int id;
		static int lastId;
		friend class Option;
	private:
		void addToSubmenus (Menu* submenu) {submenus.emplace_back(submenu);}  // *** Added
		void setParent (Menu* parentMenu) {parent = parentMenu;}  // *** Added
};

Menu::Option::Option (const char* foodName, const double cost, const Menu* menu): submenu (menu) {  
	name = new char [strlen (foodName) + 1];
	strcpy (name, foodName);
	price = cost;
	next = nullptr;
}

Menu::Option::~Option() {
	delete name;
}

void Menu::Option::Print() const {
	cout << name;
	if (!submenu)
		cout << "....." << price;
	else
		cout << " -> (submenu)";
	cout << endl;
}

int Menu::Option::Choose() const {
	if (!submenu)
		return NO_SUBMENU;
	else {
		cout << endl << "Submenu entered." << endl;
		return submenu->Choose();
	}
}

int Menu::lastId = 0;

Menu::~Menu() {
	Menu::Option *current, *next;
	for (current = first; current != nullptr; current = next) {
		next = current->Next();
		delete current;
	}
}

void Menu::Insert (const char *str, const double cost, Menu* submenu, const int pos) {
	Menu::Option *newOption = new Menu::Option (str, cost, submenu);
	Menu::Option *current, *prev = nullptr;
	int idx = 0;
	if (submenu) {  // ******************************** Added
		addToSubmenus(submenu);
		submenu->setParent(this);
	}
	for (current = first; current != nullptr && idx++ != pos; current = current->Next())
		prev = current;
	if (!prev) { 
		newOption->Next() = first;
		first = newOption;
	} 
	else { 
		newOption->Next() = current;
		prev->Next() = newOption;
	}
}

void Menu::Delete (const int pos) {
	Menu::Option *current, *prev = nullptr;
	int idx = 0;
	for (current = first; current != nullptr && current->Next() != nullptr && idx++ != pos; current = current->Next())
		prev = current;
	if (current) {
		if (!prev)
			first = current->Next();
		else
			prev->Next() = current->Next();
		delete current;
	}  
}

int Menu::Print() const {
	int n = 0;
	Menu::Option *current = first;
	for (current = first; current != nullptr; current = current->Next()) {
		cout << ++n << ". ";	
		current->Print();
	}
	return n;
}

int Menu::Choose() const {
	int n, choice;
	Menu::Option *current = first;
	do {
		cout << "Please choose an option:" << endl;
		n = this->Print();
		cout << "Option? ";
		cin >> choice;
	} while ( (choice < 1 || choice > n) && (choice != GO_BACK) );
	if (choice == GO_BACK) {  // ***************************** The desired implementation
		std::cout << "\nGoing back up one menu..." << std::endl << std::endl;
		if (!parent) throw ("Error! No parent.");
		return parent->Choose();	
	}
	n = 1;
	for (current = first; n != choice && current != nullptr; current = current->Next())
		++n;
	n = current->Choose();
	return (n == NO_SUBMENU ? choice : n);
}

int main() {  // Client code needs no change.  :)
	Menu menu;
	menu.Insert ("Hamburger", 3.49);
	menu.Insert ("Fries", 1.89);
	menu.Insert ("Taco", 0.99);
	menu.Insert ("Coke", 1.49, nullptr, 1);
	
	Menu wineMenu;
	wineMenu.Insert ("Red wine", 29.99);
	wineMenu.Insert ("White wine", 39.99);
	menu.Insert ("Wine", 0, &wineMenu);
	
	Menu vintageWineMenu;
	vintageWineMenu.Insert ("Chateau Talbot", 450.00);
	vintageWineMenu.Insert ("Domaine de la Romanee", 1500.00);
	vintageWineMenu.Insert ("Penfolds Grange Bin 95", 2700.00);
	wineMenu.Insert ("Vintage Wine", 0, &vintageWineMenu, 0);
	
	int menuChoice = menu.Choose();
	cout << endl << "You chose option " << menuChoice << "." << endl;
}


Sample run:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Please choose an option:
1. Hamburger.....3.49
2. Coke.....1.49
3. Fries.....1.89
4. Taco.....0.99
5. Wine -> (submenu)
Option? 5

Submenu entered.
Please choose an option:
1. Vintage Wine -> (submenu)
2. Red wine.....29.99
3. White wine.....39.99
Option? 1

Submenu entered.
Please choose an option:
1. Chateau Talbot.....450
2. Domaine de la Romanee.....1500
3. Penfolds Grange Bin 95.....2700
Option? 0

Going back up one menu...

Please choose an option:
1. Vintage Wine -> (submenu)
2. Red wine.....29.99
3. White wine.....39.99
Option? 0

Going back up one menu...

Please choose an option:
1. Hamburger.....3.49
2. Coke.....1.49
3. Fries.....1.89
4. Taco.....0.99
5. Wine -> (submenu)
Option? 1

You chose option 1.
Last edited on
Topic archived. No new replies allowed.