Реализация BuyGood {id} {count}

Не так просто, не так лаконично и скорей всего заставит вас подняться и уйти на срочную чашку кофе.

Сложность реализации этой команды заключается в том, что нам нужно решить немного больше задач, чем в прошлый раз. Во-первых, нам нужно выполнить основную задачу, без которой все рядом стоящие аспекты не имеют смысла – продать товар. А именно снять деньги в зависимости от товара и его количества.

Во-вторых, нам нужно спарсить (примечание: парсинг – процесс сопоставления линейной последовательности лексем (слов, токенов) естественного или формального языка с его формальной грамматикой.). В данном контексте под парсингом подразумевается разбор строки “10 5” на две переменные. id и count в типе int. После парсинга нам нужно произвести процесс валидации – проверки того, что данные корректные. Его не стоит смешивать с процессом парсинга, так как разбор множества чисел из некой последовательности символов не должен быть связан с моделью приложения (по крайней мере, в тех терминах, в которых работаем мы).

Здесь подразумевается валидация модели. Т.е если запрашивается товар под id 10, но его нет, то данные с одной стороны – корректные – id действительно представляется целым числом, как и просили. Но с другой стороны в данный момент такого товара нет.

Мы четко разделим реализацию команды на:

  1. Разбиение строки на единицы данных;
  2. Сопоставление этих данных с переменными (и их типами);
  3. Проверка корректности этих данных на основе текущего состояния модели;

Перейдем к полной реализации:

Фрагмент 2.6     

else if (command.StartsWith("BuyGood"))
{
    //Разбиение строки на единицы данных
    string[] rawData = command.Substring("BuyGood ".Length).Split(' ');

    //Сопоставление этих данных с переменными (и их типами) 
    if(rawData.Length != 2)
    {
        Console.WriteLine("Неправильные аргументы команды");
        break;
    }

    int id = Convert.ToInt32(rawData[0]);
    int count = Convert.ToInt32(rawData[1]);

    //Проверка корректности этих данных 
    //на основе текущего состояния модели.
    if(id < 0 || id >= names.Length)
    {
        Console.WriteLine("Такого товара нет");
        break;
    }

    if(count < 0 || count > availableQuantity[id])
    {
        Console.WriteLine("Нет такого количества");
        break;
    }

    //Выполнение
    if(balance >= prices[id] * count)
    {
        balance -= prices[id] * count;
        availableQuantity[id] -= count;
    }
}

Тут вы можете наблюдать чёткие шаги, которые ведут нас к цели. Если действовать по принципу “разделяй и властвуй” – всё сложное можно привести к набору банальностей. В дальнейшем, мы эти банальности оформим более формально и всё станет ещё чётче.

Пока что нас с вами могут заинтересовать тонкости реализации. Всё начинается с небольшого отличия от других команд. Давайте вернёмся к общему каркасу (Фрагмент 2.2). Обратите внимание, что в операторах if и else if первые две команды сопоставляются через полное сравнение. А в случае с BuyGood мы используем метод StartWith.

Он позволяет проверить “начинается ли определённая строка с какой-то подстроки?”. Так мы проверяем, что если пользователь ввёл “BuyGood 10 5”, то всё хорошо, это наша команда. Проверка через равенство не сработала бы потому, что всё, что после BuyGood в строке, варьируется.

StartWith имеется простую сигнатуру :6


public bool StartsWith(string value)


Параметры:

  • value
    • Type: System.String
    • Строка, подлежащая сравнению.

Возвращаемое значение :

Type: System.Boolean

Значение true, если параметр value соответствует началу данной строки; в противном случае — значение false.

Далее начинается сама команда, с первого шага которой – мы достаем из строки команды наши данные, которые нужны для её выполнения (аргументы).

Фрагмент 2.7  

string[] rawData = command.Substring("BuyGood ".Length).Split(' ');

Начинается это с использования метода Substring.

public string Substring(int startIndex)

Параметры

  • startIndex
    • Type: System.Int32
    • Отсчитываемая от нуля позиция первого знака подстроки в данном экземпляре.

Возвращаемое значение:

Type: System.String

Строка, эквивалентная подстроке, которая начинается с startIndex в данном экземпляре или Empty, если значение startIndex равно длине данного экземпляра.

Т.е это просто функция, которая возвращает подстроку от строки. Мы её используем в таком контексте: просто вырезаем начало строки из строки, которую ввел пользователь. В результате мы получим только часть “10 5”, если пользователь введёт “BuyGood 10 5”.

Дальше, у этой строки мы сразу же вызываем метод Split. Он разбивает строку на массив строк по определённому признаку.

public string[] Split(params char[] separator)

Параметры:

  • separator
    • Type: System.Char[]
    • Массив символов, разделяющий подстроки в данной строке, пустой массив, не содержащий разделителей или null.

Возвращаемое значение:

Type: System.String[]

Массив, элементы которого содержат подстроки из этого экземпляра, разделенные символами из separator. Дополнительные сведения см. в разделе “Примечания”.

Разбор метода Split ищите в дополнительных материалах. Сейчас же мы с помощью него получили массив строк, содержащий два элемента {“10”, “5”}. И настает следующий шаг.

Фрагмент 2.8     

if(rawData.Length != 2)
{
    Console.WriteLine("Неправильные аргументы команды");
    break;
}

int id = Convert.ToInt32(rawData[0]);
int count = Convert.ToInt32(rawData[1]);

У нас есть  массив какой-то длины. Сначала мы удостоверимся, что в нём есть нужное количество токенов, которое нас интересует. Делаем это через проверку длины массива. Если длины не хватает, то с помощью break прерываем цикл, что приводит пользователя к вводу новой команды.  Я думаю, вы заметили, что сначала мы пользователю сообщаем об ошибке, а потом прерываем выполнение. К сожалению, у нас такая структура программы, что это сообщение он не увидит, так как в начале цикла у нас происходит очистка консоли.

Но давайте перейдём к сути. Сразу после того, как мы убедились, что у нас есть необходимое количество данных. Мы начинаем превращать наши строки и распределять их по переменным с типом int.

Если пользователь введёт не число, конечно, у нас выбьется ошибка. И по хорошему нам нужно заменить вызов метода ToInt32 на int.TryParse. Но видит бог я этого не хочу! Писать плохо – это искусство. Но я им сыт , я бы хотел в этот раз не показывать наглядный пример дублирования кода. Просто в следующем разделе создам необходимую нам функцию и просто воспользуюсь ей.

Фрагмент 2.9     

if(id < 0 || id >= names.Length)
{
    Console.WriteLine("Такого товара нет");
    break;
}

if(count < 0 || count > availableQuantity[id])
{
    Console.WriteLine("Нет такого количества");
    break;
}

//Выполнение
if(balance >= prices[id] * count)
{
    balance -= prices[id] * count;
    availableQuantity[id] -= count;
}

Наконец-то мы добрались до фрагмента, в котором валидируем данные с точки зрения модели приложения и, если всё хорошо, совершаем продажу товара.

Все вместе эти команды  и состояние нашего автомата формируют его. Давайте возьмем эту реализацию за отправную точку в нашем последующем прогрессе?

Листинг WendingMachine/ArraysAndСycles/Program.cs     

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Code
{
    class Program
    {
        static void Main(string[] args)
        {
            int balance = 0;
            int[] coinsQuantity = { 0, 0, 0, 0}; //1, 2, 5, 10
            int[] coinsValues  = { 1, 2, 5, 10}; 
            string[] names = { "Шоколадка", "Газировка" };
            int[] prices = { 70, 60 };
            int[] availableQuantity = { 5, 2 };
            PaymentType payment = PaymentType.Card;

            string command = "";
            while (true)
            {
                Console.Clear();
                Console.WriteLine($"Баланс {balance}");
                Console.WriteLine("Введите команду:");
                command = Console.ReadLine();

                if(command == "AddMoney")
                {
                    switch (payment)
                    {
                        case PaymentType.Coins:
                            for(int i = 0; i < coinsValues.Length; i++)
                            {
                                Console.WriteLine($"Сколько монет номиналом {coinsValues[i]} вы хотите внести?");
                                int count = 0;
                                while (!int.TryParse(Console.ReadLine(),
                                                        out count))
                                {
                                    Console.WriteLine("Вы ввели не число!");
                                }
                                coinsQuantity[i] += count;
                                balance += count * coinsValues[i];
                            }
                            break;
                        case PaymentType.Card:
                            Console.WriteLine("Сколько снять с вашей карты?");
                            int balanceDelta = 0;
                            while (!int.TryParse(Console.ReadLine(),
                                    out balanceDelta)) 
                            {
                                Console.WriteLine("Вы ввели не число!");
                            }
                            balance += balanceDelta;
                            Console.WriteLine("Баланс успешно пополнен");
                            break;
                        default:
                            break;
                    }
                }
                else if(command == "GetChange")
                {
                    balance = 0;
                }
                else if (command.StartsWith("BuyGood")) 
                {
                    //Разбиение строки на единицы данных
                    string[] rawData = command.Substring("BuyGood ".Length).Split(' ');

                    //Сопоставление этих данных с переменными (и их типами) 
                    if(rawData.Length != 2)
                    {
                        Console.WriteLine("Неправильные аргументы команды");
                        break;
                    }

                    int id = Convert.ToInt32(rawData[0]);
                    int count = Convert.ToInt32(rawData[1]);

                    //Проверка корректности этих данных на основе текущего состояния модели.
                    if(id < 0 || id >= names.Length)
                    {
                        Console.WriteLine("Такого товара нет");
                        break;
                    }

                    if(count < 0 || count > availableQuantity[id])
                    {
                        Console.WriteLine("Нет такого количества");
                        break;
                    }

                    //Выполнение
                    if(balance >= prices[id] * count)
                    {
                        balance -= prices[id] * count;
                        availableQuantity[id] -= count;
                    }
                }
                else
                {
                    Console.WriteLine("Команда не определена");
                }

                Console.ReadKey();
            }
        }
    }

    enum PaymentType
    {
        Coins,
        Card
    }
}

Если вы нашли ошибку, пожалуйста выделите её и нажмите Ctrl+Enter.


Leave a Reply

Your email address will not be published. Required fields are marked *