As in the previous question, and in this, I would choose something like a state machine. For example, we define a state interface
public interface IState { IState RunState(); }
What is the essence of the menu? To start the menu
public class MenuItem { public string Text {get;set;} }
Further, the state where there are menu items and where the user can select these items. For example, a simple base class:
public abstract class MenuState : IState { protected abstract Dictionary<int, MenuItem> Menus { get; } protected virtual void ShowMenu() { Console.WriteLine("You have options:"); foreach (var m in Menus) Console.WriteLine($"{m.Key} - {m.Value.Text}"); } protected virtual KeyValuePair<int, MenuItem> ReadOption() { Console.WriteLine("Please, select option:"); ShowMenu(); var str = Console.ReadLine(); int answerId = 0; if (int.TryParse(str, out answerId)) { if (!Menus.ContainsKey(answerId)) { Console.WriteLine("Selected item notexists."); return ReadOption(); } return new KeyValuePair<int, MenuItem>(answerId, Menus[answerId]); } else { Console.WriteLine("Selected item not a number."); return ReadOption(); } } public virtual IState RunState() { var option = ReadOption(); return NextState(option); } protected abstract IState NextState(KeyValuePair<int, MenuItem> selectedMenu); }
As an example of a simple state with several points.
public class MenuState1 : MenuState { private Dictionary<int, MenuItem> _menus = new Dictionary<int, MenuItem>() { {1, new MenuItem(){Text = "Menu 1"}}, {2, new MenuItem(){Text = "Menu 2"}}, {3, new MenuItem(){Text = "Menu 3"}}, {4, new MenuItem(){Text = "Exit"}}, }; protected override Dictionary<int, MenuItem> Menus => _menus; protected override IState NextState(KeyValuePair<int, MenuItem> selectedMenu) { if (selectedMenu.Key == 4) return null; if (selectedMenu.Key == 1) return new AuthState(); return this; } }
Well, or here's the state without the menu
public class AuthState : IState { public IState RunState() { Console.WriteLine("Login: "); var login = Console.ReadLine(); Console.WriteLine("Password: "); var password = Console.ReadLine(); Console.WriteLine($"Hello, {login}"); return new MenuState1(); } }
How to use it all:
IState startState = new AuthState(); while(startState!=null) startState = startState.RunState();
As an example of output
Login: Password: Hello, Vasya Please, select option: You have options: 1 - Menu 1 2 - Menu 2 3 - Menu 3 4 - Exit
Of the benefits - each state knows only about the next state, and even this can be delegated to a third-party class.
Well, or you can make the state custom, for example
public class ConfigurableMenuState : MenuState { private Dictionary<int, MenuItem> _menus = new Dictionary<int, MenuItem>(); private Dictionary<int, Func<IState>> _transitions = new Dictionary<int, Func<IState>>(); protected override Dictionary<int, MenuItem> Menus => _menus; protected override IState NextState(KeyValuePair<int, MenuItem> selectedMenu) { return _transitions[selectedMenu.Key](); } public void AddMenuItem(int id, MenuItem menu, Func<IState> nextState) { _menus.Add(id, menu); _transitions.Add(id, nextState); } }
How to use
var menuState = new ConfigurableMenuState(); menuState.AddMenuItem(1, new MenuItem() {Text = "Option 1"}, ()=> menuState); menuState.AddMenuItem(2, new MenuItem() {Text = "Exit"}, ()=> null); IState startState = menuState; while(startState!=null) startState = startState.RunState();
Conclusion
Please, select option: You have options: 1 - Option 1 2 - Exit
Of the advantages - the class is autonomous, there are no dependencies, I do not want to test.