LINUX.ORG.RU

Qt: повторяющиеся блоки switch в слотах для определения типа объекта

 


2

2

Всем привет.

Проблема вроде бы и простая, но не могу придумать нормальное решение.

Есть одна клиент-серверная программа. Есть несколько типов («B», «C», «D»), унаследованных от интерфейса («A»). Объектами этих типов перекидываются между собой всякие классы типа БД-воркера, TCP-сервера, менеджера устройств, etc. Чтобы не загромождать последние кучей сигналов-слотов на каждый тип, в сигналах-слотах передаются смарт-поинтеры на объект типа A, после чего в слоте проверяется, что там за тип вызовом метода а-ля get_type(), указатель приводится к действительному типу и соответствующе обрабатывается. В итоге код загромождается этими switch'ами, что ведёт к понятным проблемам, если понадобится добавить ещё один тип — придётся бегать и править все эти портянки.

Вроде как, можно применить другой способ — перегрузку сигналов/слотов, но тогда для каждого перегруженного сигнала/слота, как я понимаю, надо будет прописывать свой connect, что тоже смотрится не очень. UPD: может нафигачить глобально доступный макрос, который будет вставлять эту кучу коннектов на каждый существующий тип? Тогда достаточно будет модифицировать макрос при введении ещё одного типа.

Хочется, чтобы при добавлении нового типа-наследника «A», места для допиливания были строго локализованы и легко определяемы.

Наверняка есть какое-то решение, до которого я не могу допетрить, может кто подсказать? Или дать ссылку на проэхт, где можно подсмотреть, как делать по красоте?



Последнее исправление: s3rjke (всего исправлений: 1)

что там за тип вызовом метода а-ля get_type(), указатель приводится к действительному типу и соответствующе обрабатывается

Это вопиющее нарушение LSP. Ты уж реши, либо это один тип объекта, тогда никаких кастов и свичей не нужно и с ними можно единообразно работать через указатель на интерфейс. Либо это разные типы объектов, тогда да, для них нужно писать отдельные слоты и на каждый слот делать connect, это абсолютно логично.

Кажется что тебе нужно рассказать что такое на самом деле B, C и D и какой там интерфейс для более предметного разговора.

Хочется, чтобы при добавлении нового типа-наследника «A», места для допиливания были строго локализованы и легко определяемы.

В некоторых случаях для этого можно заюзать visitor.

slovazap ★★★★★
()

Хочется, чтобы при добавлении нового типа-наследника «A», места для допиливания были строго локализованы и легко определяемы.

Сделай мапы вместо свичей :) А обработку делегируй :)

anonymous
()

в твоем случае будет работать старый стиль connect SIGNAL SLOT который работает в рантайме

новомодные connect(obj, &Class:Signal, this, ...) не будут.

а потом хоть qobject_cast sender()

bhfq ★★★★★
()
Последнее исправление: bhfq (всего исправлений: 1)
Ответ на: комментарий от slovazap

Это вопиющее нарушение LSP.

не нужно либо нужно

Да кого колыхает это квадратно-гнездовое ООП в С++ 2021-м… Есть жы шаблоны и лямбды.

anonymous
()

Посетитель — это поведенческий паттерн проектирования, который позволяет добавлять в программу новые операции, не изменяя классы объектов, над которыми эти операции могут выполняться.

fluorite ★★★★★
()
Ответ на: комментарий от slovazap

Это вопиющее нарушение LSP.

Можно расшифровку? Гугл много разного не по теме выдаёт.

Кажется что тебе нужно рассказать что такое на самом деле B, C и D и какой там интерфейс для более предметного разговора.

Пришлось поднапрячь память. В общем, программулина умеет работать по сетке с разными устройствами. Классам, которые работают с устройствами, при создании объектов надо выдать настройки (IP, port и ещё всякое). Но некоторые классы требуют дополнительных настроек, в силу специфики самих железяк. Я сделал некую общую структуру, а-ля

struct TcpDeviceParams
{
int32_t type;
QString ip;
uint16_t port;
...прочие общие для всех поля
}

От неё наследуются структуры со специфическими дополнительными параметрами.

struct SpecificDeviceParamsOne : public TcpDeviceParams
{
uint16_t incoming_port;
int32_t reset_timeout;
}

struct SpecificDeviceParamsTwo : public TcpDeviceParams
{
uint16_t http_port;
}

И всякое в таком духе. Соответственно, чтобы между компонентами программы гонять настройки приборов через сигналы/слоты, используется смарт-поинтер на TcpDeviceParams, в слоте (если требуются какие-то специфические действия для конкретного типа) проверяется поле type.

Это касается не только сетевых настроек, классы устройств передают другим компонентам программы всякие устройство-специфичные данные, для которых заведены похожие структуры (а-ля DeviceStatus, от которого наследуется DeviceStatusOne, DeviceStatusTwo и т.д., дополняющие родителя своими специфичными полями).

В итоге получаются свитчи в слотах, чтобы это дело разбирать. Поначалу показалось, что «вроде не так страшно», но по мере написания кода стало как-то неуютно. Но и куча коннектов/перегрузок на каждый тип мне кажется не очень красивым подходом (в другой программе шёл этим путём).

s3rjke
() автор топика
Ответ на: комментарий от bhfq

в твоем случае будет работать старый стиль connect SIGNAL SLOT который работает в рантайме

С ним вылезает проблема навигации по коду. Когда тычешь правой кнопкой мыши по сигналу/слоту и выбираешь «найти использование». На макросах нифига не находит, очень неудобно.

s3rjke
() автор топика
Ответ на: комментарий от slovazap

Это вопиющее нарушение LSP.

В некоторых случаях для этого можно заюзать visitor.

Прекратите думать в этой бутылке и жизнь наладится. Некоторые уже начали что-то подозревать про все эти смешные аббревиатурки из 90-х и прочие «паттерны» для желающих выключить голову и кодить не приходя в сознание.

Для просветления: https://www.yegor256.com/2017/03/28/solid.html

https://codemanship.wordpress.com/2021/03/03/forget-solid-say-hello-to-shoc-principles-for-modular-design/

https://www.quora.com/How-are-SOLID-principles-applied-to-non-object-oriented-programs-using-modules-and-functions

https://medium.com/swlh/forget-the-s-for-solid-what-matters-is-delivering-your-app-aec1e54653da

https://www.reddit.com/r/programming/comments/5qto27/why_every_element_of_solid_is_wrong/

anonymous
()
Ответ на: комментарий от s3rjke

Можно расшифровку? Гугл много разного не по теме выдаёт.

Не трать время. Читай аццов компутер саенц, линейную алгебру, теории множеств, графов, категорий и алгоритмов, а не эти подпорки для «серийных погромистов».

anonymous
()
Ответ на: комментарий от anonymous

Читай аццов компутер саенц, линейную алгебру, теории множеств, графов, категорий и алгоритмов, а не эти подпорки для «серийных погромистов».

Да мне бы что попроще, на тему организации и проектирования собственного кода, чтобы «я из будущего» тратил минимум усилий на его понимание и модификацию. Для освоения фундаментально-умных вещей нет ни мотивации, ни способностей.

s3rjke
() автор топика

Вы смеётесь что-ли! Вы руками типы объектов проверяете и приводите! При этом пишете про воркеры и смарт пойнтеры! Я в непонятках.

Это сейчас так учатся, да! То есть сложную современную архитектуру мы осилили, а ПОЛИМОРФИЗМ почему-то нет. Как было принято в нулевых - эти свитчи заменяются на спроектированную под задачу иерархию классов с виртуальными методами. Это классика для ООП плюсов и делфи! Стыдно не знать!

pup_kin
()
Последнее исправление: pup_kin (всего исправлений: 2)
Ответ на: комментарий от pup_kin

Это сейчас так учатся, да!

Это сейчас так «не учатся».

То есть сложную современную архитектуру мы осилили,

Не осилили.

Это классика для ООП плюсов и делфи!

Речь про Qt, как уменьшить объём кода, в частности избежать написания кучи коннектов на каждую перегрузку. В коннектах пишутся указатели на конкретные функции конкретных классов. А хочется обходиться одним коннектом, чтобы когда в программу добавятся новые типы, не ходить по коду и не править все блоки этих самых коннектов.

s3rjke
() автор топика
Ответ на: комментарий от s3rjke

Ну тогда извините!

Я бы в таких условиях поименил функции IDE по замене и рефакторингу. А также копировать - вставить.

pup_kin
()
Последнее исправление: pup_kin (всего исправлений: 1)
Ответ на: комментарий от pup_kin

то, что ты описал это будет работать только со старым стилем connect через макросы SIGNAL SLOT, но тсу это не подходит.

bhfq ★★★★★
()
Ответ на: комментарий от anonymous

Дело в том, что SOLID, паттерны и KISS - это не законы физики. Их нужно рассматривать как некий начальный вектор движения или решение для определенных случаев. А проблемой они начинают становиться, когда превращаются в религию и начинают использоваться «потому что» даже тогда, когда по объективным причинам лучше сделать без них.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 2)

проверяется, что там за тип вызовом метода а-ля get_type(), указатель приводится к действительному типу и соответствующе обрабатывается

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

rumgot ★★★★★
()
Ответ на: комментарий от pup_kin

Да блин. Не все ты полиморфизмом решишь. У потомка просто может быть добавлен новый метод, которого нет у предка. И что тут полиморфизм сделает? Типа как событие QInputEvent предок и QMouseEvent потомок. Ясно, что никакого знания о том, что в событии мыши есть методы для доступа к координатам, у предка нету. Все равно кастить нужно (например данные действия потребуются при применении QObject::installEventFilter()).

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 3)
Ответ на: комментарий от rumgot

Но если ты пишешь функцию, которая работает с QMouseEvent, нахрена ты ей передаёшь указатель на QInputEvent?

unC0Rr ★★★★★
()
Ответ на: комментарий от rumgot

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

Ну... Проблема в том, что она будет во многих местах. И «будущий я», когда будет модифицировать код, может тупо забыть про все места программы, где нужно дописать эту логику при добавлении нового типа (точнее, нескольких типов, поскольку добавление нового устройства влечёт за собой несколько новых типов). Как бы, достаточно будет, конечно, потыкаться руками, чтобы увидеть, что не работает и за разумный срок найти все нужны места.

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

s3rjke
() автор топика
Ответ на: комментарий от bhfq
connect(spinbox, qOverload<int>(&QSpinBox::valueChanged), slider, &QSlider::setValue);
unC0Rr ★★★★★
()
Ответ на: комментарий от unC0Rr

Но если ты пишешь функцию, которая работает с QMouseEvent, нахрена ты ей передаёшь указатель на QInputEvent?

Я же указал в скобках:

например данные действия потребуются при применении QObject::installEventFilter().

Тут конечно нужно уточнить, что передается указатель не на QInputEvent, а на предка еще более дальнего - QEvent.

rumgot ★★★★★
()
Ответ на: комментарий от s3rjke

И «будущий я», когда будет модифицировать код, может тупо забыть про все места программы, где нужно дописать эту логику при добавлении нового типа

Напиши рядом коментарий, в каких местах еще надо крутить :) Если будущий ты не забудет русский/онглицкий, то разберется как-нибудь.

Xintrea ★★★★★
()

visitor

/thread

anonymous
()
Ответ на: комментарий от unC0Rr

Да согласен. Но QObject::installEventFilter() как раз для тех случаев, когда нужно быстро интегрировать некую функциональность в существующую архитектуру без ее полной реорганизации.

rumgot ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.