Часть 2 — Бизнес-логика

В сегодняшнем уроке мы собираемся построить бизнес-логику калькулятора в Swift, используя модель MVVM с учетом лучших практик.

Построение представления описано в Часть 1.

Более того, вполне нормально начать отсюда, если вы не заинтересованы в построении представлений. Идите вперед и скачайте стартовый проект.

API калькулятора

Наша цель — сделать модель Calculator полностью независимой.

Согласно принципу единой ответственности, каждый модуль, класс или функция должны иметь единую ответственность.

Калькулятор выполняет расчеты; поэтому он должен быть сосредоточен только на получении входных данных, вычислении и возврате результата. Он не несет ответственности ни за что, связанное с просмотром.

Это дает нам возможность использовать Calculator где угодно. Мы можем использовать его в нашем уже существующем CalculatorView, в модульных тестах, в другом совершенно другом представлении калькулятора, в CLI и т. д.

Чтобы сделать это возможным, нам нужно объявить API. — какие функции и свойства могут быть доступны вне для использования нашего Калькулятора?

Давайте подумаем, какие свойства и функции необходимы извне, чтобы иметь возможность полноценно работать с калькулятором (это будут наши public свойства и функции). — Что в нашем распоряжении с помощью реального калькулятора?

Для свойств нам нужен способ чтения отображаемого в данный момент числа.

Для функций нам нужно выполнить действие для каждого случая ButtonType.

Давайте начнем.

Внутри Models создайте новый файл Swift с именем Calculator

Добавьте следующий шаблонный код:

Модель представления

Прежде чем продолжить с Calculator, давайте сначала подключим наше представление, чтобы мы могли сразу начать его тестирование.

Ниже CalculatorView.swift создайте новый файл Swift с именем CalculatorViewModel:

В CalculatorViewModel.swift создайте final class имя ViewModel, соответствующее протоколу ObservableObject и находящееся внутри нашего CalculatorView

Наш ViewModel будет включать в себя заказ buttonTypes и экземпляр модели Calculator. Нам также нужно добавить необходимые свойства и функции, чтобы иметь возможность работать с файлом Calculator.

Вот код для CalculatorViewModel.swift:

В CalculatorView.swift добавьте новое частное свойство @EnvironmentObject для ссылки на наш ViewModel.

Обязательно обновите компоненты displayText и buttonPad, чтобы получить viewModel.displayText и viewModel.buttonTypes.

Вот код:

Теперь нам нужно уведомить ViewModel о нажатии кнопки калькулятора.

В CalculatorButton.swift добавьте ViewModel как объект среды и добавьте viewModel.performAction(for: buttonType) для действия кнопки.

Наконец, в CalculatorApp.swift создайте экземпляр ViewModel как environmentObject

Теперь наши View и наши ViewModel полностью подключены! View уведомляет ViewModel о нажатии кнопки, а ViewModel обновляет отображаемый текст, показанный в View.

Калькулятор

Теперь мы начнем добавлять бизнес-логику, чтобы заставить Calculator работать. Мы будем добавлять каждую из наших функций API одну за другой.

Установить цифру

Вернитесь к Calculator.swift

Прежде чем начать с логики установки цифр, нам понадобятся эти свойства, поэтому добавьте их вверху:

Внизу добавьте следующие вспомогательные функции

При установке цифры нам необходимо:

  1. Проверьте, можете ли вы добавить цифру (01 не должно быть возможно).
  2. Преобразование десятичного числа newNumber в строку
  3. Добавьте digit в конец строки, преобразуйте строку обратно в десятичную и присвойте ее новое значение newNumber.

Вот код для установки цифры:

Установить операцию

Нам нужно создать новую структуру с именем ArithmeticExpression, чтобы упростить вычисление арифметических выражений.

Добавьте эту структуру в вашу модель Calculator:

Добавьте свойства expression и result и обновите вычисляемое свойство number.

Теперь для настройки операции нам необходимо:

  1. Проверьте, есть ли номер, который мы можем использовать (newNumber или предыдущий result), и назначьте его новой переменной number
  2. Проверьте, есть ли уже existingExpression, если есть, оцените его с помощью number и присвойте результат number
  3. Назначьте новый ArithmeticExpression с number и operation на expression
  4. Сбросить newNumber

Вот код:

Теперь наш Калькулятор работает для цифр и операций. Давайте просто добавим еще несколько визуальных очередей, чтобы знать, активна ли операция в данный момент.

Мы выделим кнопку операции, если пользователь только что нажал ее.

Добавьте следующую вспомогательную функцию в Calculator.swift

Добавьте следующую вспомогательную функцию в CalculatorViewModel.swift

И, наконец, добавьте следующие вспомогательные функции в CalculatorButton.swift

В том же файле обновите foregroundColor и backgroundColor стиля кнопки CalculatorButton, чтобы получить только что добавленные вспомогательные функции.

Теперь у нас есть подсветка кнопок управления!

Оценивать

Это просто.

Для оценки нам необходимо:

  1. Развернуть newNumber и expression (выражение содержит предыдущее число и операцию)
  2. Оцените expression с помощью newNumber и назначьте result
  3. Сбросить expression и newNumber

Вот код:

Установить процент

Чтобы установить процент, нам нужно:

  1. Проверьте, используется ли в настоящее время newNumber или result
  2. Разделите на 100 и присвойте новое значение

Вот код:

Переключить знак

toggleSign() очень похоже на setPercent(), так как нам нужно проверить, используется ли в настоящее время newNumber или result, и применить операцию (в этом случае добавить знак минус).

Тем не менее, есть сложная часть. — Мы не можем добавить знак минус к 0.

Решение?

Мы можем добавить отрицательный знак к строке, в частности к displayText. Нам просто нужно знать, когда и когда не вставлять отрицательный знак.

Как и в элементарной математике, мы будем использовать переносы (9 + 8 = 17, перенос 1).

Добавьте это свойство вверху под private var result: Decimal?

private var carryingNegative: Bool = false

Таким образом, если существует newNumber или result, примените отрицательный знак, в противном случае сделайте свойство carryingNegative истинным. (Останется истинным, пока не будет установлено новое число)

Добавьте этот код в toggleSign()

Свойство carryingNegative используется во вспомогательной функции getNumberString()

Помните, когда newNumber, expression.number, и result равны нулю, значение по умолчанию равно 0? Благодаря свойству carryingNegative мы можем вставить строку отрицательного знака в 0, чтобы вернуть «-0» displayText так же, как визуальную очередь, что отрицательный знак активен.

Обновите getNumberString() следующим кодом:

Наконец, нам нужно деактивировать отрицательный перенос, когда установлено newNumber.

Добавьте этот код в newNumber:

Установить десятичное число

Установка десятичной дроби может быть самой сложной частью.

Теперь нам нужно добавить логику переноса к двум вещам. До десятичной точки и нулей после запятой.

Десятичная точка

Наше свойство newNumber имеет тип Decimal. Так что не существует таких вещей, как 5. или 15. , это целые числа. Однако, если пользователь нажимает кнопку с десятичным числом, нам нужно предоставить визуальную очередь, в которой устанавливается десятичное число. Нам нужно обновить displayText, пока мы не сможем установить newNumber в соответствующее десятичное число. Для этого мы будем использовать новое свойство с именем carryingDecimal.

Давайте запустим быстрый пример для ввода 5.2

  1. Установите цифру 5 (newNumber = 5, carryingDecimal = false и displayText = “5”)
  2. Установите десятичное число (newNumber = 5, carryingDecimal = true и displayText = “5.”)
  3. Установите цифру 2 (newNumber = 5.2, carryingDecimal = false и displayText = “5.2”)

Добавить свойство carryingDecimal

Добавить containsDecimal вычисляемое свойство

Фактическая десятичная функция настройки очень проста:

  1. Проверить, содержит ли уже number десятичное число, если да, вернуть
  2. Сделайте свойство carryingDecimal true

Вот код:

Наконец, сбросьте carryingDecimal, когда установлено newNumber

Нали после запятой

Теперь нам предстоит решить похожую задачу. При установке нулей после десятичной точки наше свойство newNumber, имеющее тип Decimal, никогда их не добавит. Что касается 5.40000, то он всегда будет преобразован обратно в 5.4, так как это не String.

Подобно тому, что мы сделали с десятичной точкой, нам нужно создать новое свойство с именем carryingZeroCount, чтобы отслеживать нули после десятичной точки и добавлять их, когда установлена ​​ненулевая цифра. Тем временем мы также покажем нули в displayText, чтобы обеспечить визуальную очередь.

Давайте рассмотрим это на примере, чтобы было понятнее. Мы хотим ввести 2.003

  1. Установите цифру 2 (newNumber = 2, carryingDecimal = false, carryingZeroCount = 0, displayText = “2”)
  2. Установить десятичное число (newNumber = 2, carryingDecimal = true, carryingZeroCount = 0, displayText = “2.”)
  3. Установите цифру 0 (newNumber = 2, carryingDecimal = true, carryingZeroCount = 1, displayText = “2.0”)
  4. Установите цифру 0 (newNumber = 2, carryingDecimal = true, carryingZeroCount = 2, displayText = “2.00”)
  5. Установите цифру 3 (newNumber = 2.003, carryingDecimal = false, carryingZeroCount = 0, displayText = “2.003”)

Я знаю, что это становится несколько запутанным, но, делая это, мы можем вообще избежать использования манипуляций со строками для ввода, что приносит свою долю проблем и сложностей.

Добавьте свойство carryingZeroCount

Обновите функцию setDigit() до:

И, наконец, добавьте переносимые нули к getNumberString()

Все чисто

Очистить все просто. Нам просто нужно сбросить все наши используемые в настоящее время свойства

Вот код:

Прозрачный

Заметили, что при использовании Apple Calculator кнопка AC (Очистить все) меняется на кнопку C (Очистить) сразу после ввода цифры или десятичного числа? Эти кнопки могут выглядеть так, как будто они делают одно и то же, сбрасывая дисплей на 0, но на самом деле у них разные функции.

Все ясно сбросил все в том числе newNumber, expression, result и возит.

Очистите только последнюю запись, в нашем случае newNumber и ее переносы.

Итак, допустим, вы хотите посчитать 5 + 3. Вы вводите 5 + 2 — Ой! Нажмите кнопку очистки (5 + все еще сохраняется), введите 3, результат = 8. Все хорошо.

Давайте приступим к этому, но сначала добавим свойство pressedClear, оно нам понадобится.

Аналогично нашей allClear() функции добавьте этот код в clear()

И обновить newNumber, чтобы при его установке мы сбрасывали pressedClear

Давайте добавим небольшое улучшение в number для лучшего взаимодействия с пользователем.

Если пользователь только что нажал очистку или десятичное число, давайте покажем 0, даже если есть активный результат или выражение.

Обновите свойство number с помощью этого кода:

Показывать кнопку AC или C в нашем представлении

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

Добавьте вычисляемое свойство showAllClear:

Вернитесь к CalculatorViewModel.swift и обновите вычисляемое свойство buttonTypes:

Окончательный калькулятор

Вот код нашей окончательной модели Calculator:

Подведение итогов

Наконец-то мы закончили создание Apple Calculator. Спасибо, что вытащили.

Мы успешно инкапсулировали каждый компонент в свою собственную функциональность. ViewModel не несет ответственности за выполнение вычислений, он просто информирует Calculator о том, что вводит пользователь, и запрашивает результат.

Созданную нами модель Calculator теперь можно использовать с любым видом. Мы даже можем создавать модульные тесты, не используя какой-либо другой файл.

Исходный код этой части вы можете найти здесь.

Спасибо за чтение, я был бы очень признателен, если бы вы подписались, чтобы я мог продолжать создавать больше подобного контента :)

Ресурсы:

  • Вы можете найти часть 1, создание представления, здесь
  • Вы можете найти репозиторий Github здесь