Maybe someone knows how to properly organize the menu calls in the logic of the console application?
Suppose there is a logic class. And many menu classes.
How in this logic to call them in one method too much code is obtained. In different methods, but these methods cause each other, what will be wrong?

If there are experts in testing, tell me how can you test methods that call other methods, or refactor the code by breaking these links?

  • Possible duplicate question: Using OOP approach in the console application - tym32167
  • @ tym32167 The reason for closing is as a duplicate "this question has already been asked and has a solution", but the question by reference does not have a solution yet. Question to TS: Why do you ask almost the same question again? - 0xdb
  • This is another question - Karl Marks
  • Hmm, maybe you are right, but at least two participants have not noticed much of a difference. And not essentially the question - add in the comments @ name - 0xdb
  • one
    @ 0xdb actually I received an alert. It is possible and just <doggy sign> Foggy will still work - user227049

1 answer 1

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.

  • Thanks, it helped a lot !!! - Karl Marks