Практика 11

Программируемый польский калькулятор

Первая версия польского калькулятора была описана тут. В ней мы изучили что такое стэк, как написать много if else и как заменить их всех на словарь операций.

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

SOLID

  • single responsibility
  • open-closed
  • Liskov substitution
  • interface segregation
  • dependency inversion

В этом задании мы концентрируемся на принципах “single responsibility” (единая ответсвенность) и “open-closed” (открытость/закрытость принцип). Особенно нас интересует open-closed - система открыта для расширения, но закрыта для модификации.

Компоненты системы

  1. Ядро системы. В нём сосредоточена бизнес логика - то как хранится стек операций, то как происходят вычисления независимо от самих операций.

  2. Система подключения операций. При запуске системы этот компонент должен найти в папке динамически все операции и подключить их к ядру.

  3. Интерфейс взаимодействия с пользователем. В простом варианте - это command line interface (CLI). Запуск может выглядеть следующим образом: python3 calc/ "5 1 2 + 4 * + 3 -".

  4. Пользовательские операции. Каждая операция - это отдельный файл внутри которого описана логика его работы и дополнительная необходимая информация для его использования ядром.

Соответсвенно принципу open-closed 1,2,3 компоненты системы модифицировать запрещено. Расширение системы операциями идет только через добавление новых файлов в 4.

Классы системы

Каждый класс лежит в отдельном модуле(файле). Если написано, что нужно создать интерфейс, то создавайте его с помощью @abstractmethod аннотации над методом, в теле которого стоит просто pass. Потом в отдельном файле создаете конкрутный класс реализацию интерфейса. Например class ListStack(Stack):.... Каждый класс должен быть протестирован.

Stack интерфейс

Обычный стек с операциями pop и push.

Calculator

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

Operator интерфейс

Интерфейс для любой операции. Функция eval считает результат выполнения конкретной опреации. В простом варианте, eval может принимать 2 аргумента - левый и правый операнд операции. В усложненной версии, может принимать переменное число аргументов. Функция symbol вернет символ этой операции, например + или add. Конкретные реализации могут называться AdditionOperator, SubtractOperation и так далее.

Tokenizer интерфейс

Сервис для разбиения входного потока сырых данных (строки) в поток токенов, с которыми может работать класс Calculator. В самом простом случае, конкретная реализация просто разбивает на пробелы всходную строку и создает для каждого элемента разбиения экземпляр Token.

Token

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

ClassLoader интерфейс

Загружает экземпляры операций откуда-то и возвращает их как список. Простая реализация может внутри себя в лоб сделать все импорты и вернуть массив. Сложная реализация - пойдет в папку, загрузит файлы, прочитает их как модули, создаст экземпляры и вернёт их.

cli.py, web.py, test.py

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

Easy

Напишите 1 компонент системы и 7 базовые операции: сложение, вычитание, умножение, деление, остаток от деления, деление нацело, возведение в степень. Загрузку классов (2 компонент) с делайте в виде прямого импорта операций в классе SimpleClassLoader.

Оформить как библиотеку. Проверка работы системы через юнит тесты.

Moderate

Сделайте так, чтобы операции могли принимать разное число аргументов, а не только 2.

Moderate+

Реализуйте динамический загрузчик операций. Реализуйте 3-ий компонент как CLI.

В результате запуска команды python3 calc/ "5 1 2 + 4 * + 3 -" должны получить в ответ одно число или понятное сообщение об ошибке, без стектрейса.

Hard

Реализуйте 3-ий компонент как сервер.

Напишите простой сервер, который принимает один единственный POST запрос со строкой команды вычисления.

Пример работы: curl -d '5 1 2 + 4 * + 3 -' -X POST http://localhost:3000. Должны получить от сервера в ответ одно число или текст ошибки.

Hint (python)

Вы можете воспользоваться importlib модулем и этим примером динамической загрузки модуля:

import importlib.util

spec = importlib.util.spec_from_file_location("module.name", "/path/to/file.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
module.MyClass()

Hint (java)

Если вы делаете на java, то вам понадобится узнать как подгрузить скомпилированный jar файл на лету, или как подгрузить и скомпилировать внутри вашего приложения другой java файл, или как подгрузить groovy скрипт операции и исполнить его.

Примеры этого функционала вы моежете найти в этом проекте

Литература