Не так просто, не так лаконично и скорей всего заставит вас подняться и уйти на срочную чашку кофе.
Сложность реализации этой команды заключается в том, что нам нужно решить немного больше задач, чем в прошлый раз. Во-первых, нам нужно выполнить основную задачу, без которой все рядом стоящие аспекты не имеют смысла – продать товар. А именно снять деньги в зависимости от товара и его количества.
Во-вторых, нам нужно спарсить (примечание: парсинг – процесс сопоставления линейной последовательности лексем (слов, токенов) естественного или формального языка с его формальной грамматикой.). В данном контексте под парсингом подразумевается разбор строки “10 5” на две переменные. id и count в типе int. После парсинга нам нужно произвести процесс валидации – проверки того, что данные корректные. Его не стоит смешивать с процессом парсинга, так как разбор множества чисел из некой последовательности символов не должен быть связан с моделью приложения (по крайней мере, в тех терминах, в которых работаем мы).
Здесь подразумевается валидация модели. Т.е если запрашивается товар под id 10, но его нет, то данные с одной стороны – корректные – id действительно представляется целым числом, как и просили. Но с другой стороны в данный момент такого товара нет.
Мы четко разделим реализацию команды на:
- Разбиение строки на единицы данных;
- Сопоставление этих данных с переменными (и их типами);
- Проверка корректности этих данных на основе текущего состояния модели;
Перейдем к полной реализации:
Фрагмент 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
}
}