Тестовое задание для GloboGames
Задача: необходимо написать консольный калькулятор, который принимает входную строку, содержащую математическое выражение и выводит в консоль его результат.
Главный (но не единственный) критерий при оценке задания – разработка с использованием принципов SOLID.
Минимальный набор поддерживаемых математических операций: сложение, вычитание, умножение и деление.
Пример:
Ввод: 23 * 2 + 45 - 24 / 5
Ответ: 86,2
Реализация заняла примерно полтора дня. Помимо базовых опрераций "( ) + - * / ^ sin" добавлена возможность расширять калькулятор любыми другими. Для добавления новой операции достаточно реализовать 2 интерфейса: IOperationFactory и IOperation и добавить реализацию IOperationFactory в IoC контейнер.
Алгоритм вычисления выражания:
- разбиваем выражение на токены, указывая их типы (число, оператор или скобки)
- используя алгоритм Shunting Yard преобразуем токены в операции, стоящие в обратной польской нотации, например: "1 + 2" => "1 2 +".
- выполняем операции, получаем результат
Программа написана в Visual Studio 2017. Но для компиляции она не нужна.
Нужен dotnet sdk 2.0. Откройте в консоли папку с солюшеном. Выполните команды
dotnet restore
dotnet build
dotnet run --project ./src/GloboCalc.ConsoleApp
Для запуска юнит-тестов:
dotnet test ./test/GloboCalc.Core.Tests
Либо вызвать калькулятор с параметрами, например:
dotnet GloboCalc.ConsoleApp.dll "1 + 2" "2 * (1 + 2)"
Либо просто запустить без параметров, тогда программа откроется в режиме диалога с пользователем.
Приложение написано с использованием принципов SOLID. Подробнее:
Каждый компонент обладает 1й обязанностью.
Например:
- Tokenizer - разбирает выражение на токены
- InfixToPostfixNotationConverter - конвертирует токены в обратную польскую нотацию
- PostfixNotationCalculator - вычисляет значение выражение из обратной польской нотации
Приложение закрыто для изменения и открыто для расширения
Например:
Для добавления новой операции не нужно менять код. Достаточно реализовать IOperationFactory и IOperation.
Пример расширения функциональности без изменения GloboCalc.Core можно увидеть в ветке custom-operation. Для примера добавлена операция логирифма по основанию 10 (log10)
Объекты могут быть заменены подтипами без изменения корректности программы.
Например:
Для того чтобы избежать switсh при создания операции на основе токена используется абстрактная фабрика IOperationFactory создающая операции IOperation. Она реализуется классом Constant и 2мя абстрактными классами BinaryOperation - для операций с двумя операндами и UnaryOperation для операций с 1м. Каждый из этих абстракнтых классов реализуется своими конкретными классами.
Несмотря на то, что опрерации IOperation настолько разные (операторы и операнды), приложение спроектировано так, что не нужно разрушать абстракцию чтобы произвести вычисления (класс PostfixNotationCalculator).
Интерфесы должны быть для конкрентого назначения.
Например:
IOperationPropertiesExtractor - предоставляет только методы для получения параметров операции, а не для всех возможностей класса AllOperationsFactory
Зависимости на абстакции.
Например:
Главный класс Calc, который производит вычисления, зависит от интерфейсов ITokenizer, IInfixToPostfixNotationConverter, IPostfixNotationCalculator. Реализации этих интерфейсов передаются в конструктор через контейнер зависимостей. Т.к. не зависит от конкретных реализаций, можно переписать любой этап разбора или вычисления выражения без изменеия уже существуещего кода.