Есть только одно правильное решение

Надо признаться , что разделяя функционал программы по классам всегда можно сделать это лучше...

Наследование, инкапсуляция , полиморфизм - всего лишь умные слова, которые возможно более путают , чем помогают.

Что же подсказывает практика?

Правильное выделение или отделение функционала залог наиболее быстрого достижения желаемого результата.

Разделяй правильно функционал и влавствуй.

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

Потом возможно выясняется, что отдельные наборы классов (назавем их модули) хотелось бы использовать в других проектах (с минимальными изменениями , а лучше вообще без изменения кода).

Хорошим примером является библиотека Qt. Механизм сигнал/слот как раз этому и способствует (разделению функционала).

Что же такое функционал? Наверное - это задачи , решаемые программой.

Задачи каждого модуля программы должны быть сугубо индивидуальными.

Например управление оборудованием (кассовый аппарат к примеру) это отдельная задача.

Вывод на экран формы для интерактивного управления приложением это отдельная задача.

Ведение логгирования приложения еще отдельная задача.

Связь с сервером в интернете и обмен информацией еще один отдельный функционал и так далее...

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

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

Когда надо что-то послать серверу делаем команду модулю и он чего-то там связывается с кем-то в интернете чего-то посылает ... и возвращает управление обратно в приложение.

Почему наш мозг выделяет эти задачи в отдельные?...

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

То есть каждый модуль занимает последовательно "время" программы, но может прерываться другим модулем. Сразу становится понятно кто от кого зависит.

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

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

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

Как развязать зависимость модуля формы и модуля оборудования? Точнее сделать зависимость не строгой. Например мы хотим включить внешний модуль оборудования в свой проект с формой, но результат выполнения оборудования надо обязательно принимать в модуль формы.

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

Вот этот принцип и надо использовать при зависимости одного модуля от другого.

И таким же способом модуль оборудования можно использовать в другом проекте.

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

Абстрактные классы

Теперь надо обратить внимание, что многие модули могут быть похожи друг на друга по функционалу настолько , что их функционал можно считать почти идентичным.

Например есть похожие по типу модули оборудования - кассовые аппараты меркурий , атол, штрих и т.д.

Они реализуют примерно одинаковые функции для управления оборудованием и возвращают примерно одинаковые результаты выполнения команд.

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

В чем тут плюс?...

Дело в том , что далее вместо того , чтобы отдельно связывать сигнал каждого оборудования можно связывать только сигнал астрактного класса и не надо обращаться к оборудованию конкретно. И это будет работать.

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

Это было то, что касается управления оборудованием. А теперь вопрос - как возвращать результат выполнения команды. Можно как значение типа int или bool возвращать из вызова emit.

Но можно также выпускать сигнал, который также нужно определить в абстрактном классе.

Таким образом сигналы в абстрактном классе полезная технология упрощающая программирование. По сути синалы это обратная связь модуля, которую вы можете использовать по своему желанию.