Иерархия консольного меню

173
28 марта 2018, 02:10

Может кто знает как правильно организовывать вызовы меню в логике консольного приложения?
Допустим есть класс логики.И много классов меню.
Как в этой логике их вызывать в одном методе слишком много получается кода. В разных методах, но эти методы вызывают друг друга, что будет неправильно?

Если есть спецы по тестированию, подскажите как можно тестировать методы которые вызывают другие методы, или отрефакторить код разорвав эти связи?

Answer 1

Как и в предыдущем вопросе, так и в этом, я бы выбрал чтото вроде машины состояний. Например, определим интерфейс состояния

public interface IState
{
    IState RunState();
}

что такое по сути состояние с меню? Для начала меню

public class MenuItem
{   
    public string Text {get;set;}
}

Далее состояние, где есть пункты меню и где юзер может эти пункты выбрать. Например, простой базовый класс:

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);
}

Как пример простого состояния с несколькими пунктами

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;
    }
}

Ну, или вот состояние без меню

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();
    }
}

Как это все использовать:

IState startState = new AuthState();    
while(startState!=null) startState = startState.RunState();

Как пример вывода

Login: 
Password: 
Hello, Vasya
Please, select option:
You have options:
1 - Menu 1
2 - Menu 2
3 - Menu 3
4 - Exit

Из плюсов - каждое состояние знает только о следующем состоянии, и даже это можно делегировать стороннему классу.

Ну, или можно сделать состояние настраиваемым, например

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);
    }
}

Как использовать

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();

Вывод

Please, select option:
You have options:
1 - Option 1
2 - Exit

Из преимуществ - класс автономный, зависимостей нет, тестируй не хочу.

READ ALSO
C# WPF поставить иконку для окна

C# WPF поставить иконку для окна

Добавил в resources новую иконку, для окна в свойстве icon указал ееВ XAML добавило

158
Что значит int[] при создании метода [требует правки]

Что значит int[] при создании метода [требует правки]

На CodeWars тонны таких заданий с такой штукой и я не понимаю что оно делает По типу public void int

158
Тестирование методов

Тестирование методов

Подскажите как нужно писать модульные тесты для методов, которые вызывают другие методы?

138
какой паттерн проектирования выбрать c#

какой паттерн проектирования выбрать c#

я новичок в программированиидошел до мысли, что без паттернов дальнейшее обучение приведёт к плохому стилю и привычкам

131