Поддержка аппаратных устройств (разные версии аппаратуры и протоколов). Какой архитектурный подход выбрать
Пишу утилиту для работы с разрабатываемой аппаратурой. Возник вопрос по организации архитектуры ПО в части абстракций аппаратуры. Суть вопроса: имеется несколько версий аппаратного блока (появился дополнительный функционал). И есть протокол общения с устройством. Протокол так же имеет несколько версий.
Чтобы получить комфортный интерфейс по работе с аппаратурой из основной части ПО, предполагаю сделать следующее. Создать класс устройства, который будет содержать все возможные методы по работе с протоколами всех версий. Но при этом у него можно будет спросить ЧТО из этого функционала он поддерживает. Дальнейшее поведение программы будет определяться ответом устройства. Мои попытки с использованием наследования оказались не продуктивными. Много классов, постоянная приводка типов...
Ранее не сталкивался с такой задачей - буду признателен если поделитесь своим опытом.
Ответы (1 шт):
Я бы сделал так:
- Создается перечислимый тип набора функций устройства.
Например Action с полным перечнем
Action.Temp
Action.Light
Action.Bell
....
- Создается абстрактный базовый класс Device в котором реализуется:
2.1. конструктор, принимающий на вход параметры текущего устройства и создающий необходимый экземпляр класса-наследника.
2.2. метод, определяющий наличие необходимого интерфейса. Например bool hasFunc(Action _action). На вход подается нужная операция, возвращается есть эта операция на данном устройстве или нет. По умолчанию возвращает false.
3.3. метод, вызывающий нужную функцию устройства anyType invoke(Action _action, anyType _parm). На вход подаются необходимые параметры. На выходе получаем результат. Передавать параметры можно либо с помощью абстрактного класса параметров, либо контейнером, либо любым другим способом, на что хватит фантазии разработчика. Метод либо делаем абстрактным, либо делаем заглушку, которая по умолчанию ничего не делает.
- Создаются классы потомки для устройств с наследованием от базового класса. Эти классы каждый должен реализовывать нужный протокол.
Например:
Device_V1_P1
Device_V1_P2
Device_V2_P1
Device_V2_P2
Device_V2_P3
В каждом классе делается реализация методов hasFunc и invoke. В методе invoke вызываются нужные методы управления устройством в зависимости от переданного в параметрах типа функции и ее параметров.
Итого:
На выходе мы имеем четкую структуру классов, реализующую нужную функциональность с единой точкой входа (базовый абстрактный класс) и очень простым интерфейсом взаимодействия.
При необходимости данная структура может дополняться и изменяться.
Аналогично можно продумать структуру классов, задающих параметры для выполнения нужных функций.
Вызывающий класс должен сначала проверить возможность вызова нужной функции, и, если это возможно, вызвать ее с нужными параметрами.
