Практика 11
Программируемый польский калькулятор
Первая версия польского калькулятора была описана тут. В ней мы изучили что такое стэк, как написать много if else
и как заменить их всех на словарь операций.
В этой практике наша цель уже другая. Мы хотим спроектировать такой калькулятор, в который пользователь сможет добавлять свои собственные операции, например, остаток от деления, возведение в степень и так далее. Самое важное, это чтобы для добавления операции ему не приходилось переписывать код ядра.
SOLID
- single responsibility
- open-closed
- Liskov substitution
- interface segregation
- dependency inversion
В этом задании мы концентрируемся на принципах “single responsibility” (единая ответсвенность) и “open-closed” (открытость/закрытость принцип). Особенно нас интересует open-closed - система открыта для расширения, но закрыта для модификации.
Компоненты системы
-
Ядро системы. В нём сосредоточена бизнес логика - то как хранится стек операций, то как происходят вычисления независимо от самих операций.
-
Система подключения операций. При запуске системы этот компонент должен найти в папке динамически все операции и подключить их к ядру.
-
Интерфейс взаимодействия с пользователем. В простом варианте - это command line interface (CLI). Запуск может выглядеть следующим образом:
python3 calc/ "5 1 2 + 4 * + 3 -"
. -
Пользовательские операции. Каждая операция - это отдельный файл внутри которого описана логика его работы и дополнительная необходимая информация для его использования ядром.
Соответсвенно принципу 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 скрипт операции и исполнить его.
Примеры этого функционала вы моежете найти в этом проекте
Литература
- SOLID принципы
- Модули в Python3. Обратите внимание на специальные файлы
__main__.py
и__init__.py
- Для работы с файлами модуль
os
- Python3 http.server
- Пример простого сервера
- Статья на русском про http.server
- Flask - это уже фреймворк для написания веб приложений, если интересно то можете взять его.