Отдельный графический интерфейс

У меня есть программа, которая (среди прочего) имеет интерфейс командной строки, который позволяет пользователю вводить строки, которые затем будут отправлены по сети. Проблема в том, что я не уверен, как связать события, которые генерируются глубоко внутри графического интерфейса, с сетевым интерфейсом. Предположим, например, что моя иерархия классов GUI выглядит так:

Графический интерфейс -> MainWindow -> CommandLineInterface -> EntryField

Каждый объект графического интерфейса содержит некоторые другие объекты графического интерфейса, и все это является частным. Теперь объект entryField генерирует событие / сигнал о том, что сообщение было введено. В настоящий момент я передаю сигнал вверх по иерархии классов, поэтому класс CLI будет выглядеть примерно так:

public:
    sig::csignal<void, string> msgEntered;

И в c'tor:

entryField.msgEntered.connect(sigc::mem_fun(this, &CLI::passUp));

Функция passUp просто снова излучает сигнал для подключения к классу-владельцу (MainWindow), пока я, наконец, не смогу сделать это в основном цикле:

gui.msgEntered.connect(sigc::mem_fun(networkInterface, &NetworkInterface::sendMSG));

Теперь это кажется действительно плохим решением. Каждый раз, когда я добавляю что-то в графический интерфейс, мне приходится подключать все это через иерархию классов. Я вижу несколько способов обойти это. Я мог бы сделать все объекты общедоступными, что позволило бы мне просто сделать это в основном цикле:

gui.mainWindow.cli.entryField.msgEntered.connect(sigc::mem_fun(networkInterface, &NetworkInterface::sendMSG));

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

Такое ощущение, что я упускаю здесь что-то важное. Есть ли чистый способ сделать это?

Примечание. Я использую GTK + / gtkmm / LibSigC ++, но не помечаю его как таковой, потому что у меня была такая же проблема с Qt. Это действительно общий вопрос.


person drby    schedule 03.02.2009    source источник


Ответы (6)


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

С концентратором pub / sub вы добавляете еще один уровень косвенности, но все еще есть дублирование - в поле entryField по-прежнему указано «событие готовности сообщения публикации», а в интерфейсе прослушивателя / контроллера / сети указано «прослушивание события готовности сообщения», так что есть общий идентификатор события, о котором должны знать обе стороны, и если вы не собираетесь жестко кодировать его в двух местах, его необходимо передать в оба файла (хотя как глобальный он не передается в качестве аргумента; что само по себе не является) т большое преимущество).

Я использовал все четыре подхода - прямое соединение, контроллер, слушатель и pub-sub - и в каждом преемнике вы немного ослабляете связь, но вы никогда не уйдете от некоторого дублирования, даже если это всего лишь идентификатор опубликованное событие.

На самом деле все сводится к дисперсии. Если вы обнаружите, что вам нужно переключиться на другую реализацию интерфейса, тогда стоит абстрагировать конкретный интерфейс как контроллер. Если вы обнаружите, что вам нужна другая логика для наблюдения за состоянием, измените ее на наблюдателя. Если вам нужно разделить его между процессами или вы хотите подключиться к более общей архитектуре, pub / sub может работать, но он вводит форму глобального состояния и не поддается проверке во время компиляции.

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

person Pete Kirkham    schedule 03.02.2009

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

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

Уловка состоит в том, чтобы определить интерфейс для сервера полностью независимо от графического интерфейса. Позже вы сможете изменить графический интерфейс, вообще не изменяя сервер.

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

person gbjbaanb    schedule 03.02.2009
comment
Вы предлагаете что-то очень похожее на MVC. - person Ismael; 03.02.2009
comment
Я нажал кнопку «Добавить комментарии» с упоминанием MVC. Тем не менее, шаблон проектирования Observer и MVC - близкие друзья: Observer используется, чтобы позволить модели взаимодействовать с представлением. - person Brian; 03.02.2009
comment
Есть много шаблонов, основанных на одном и том же принципе. Разделение вещей - это вообще хорошо. - person gbjbaanb; 03.02.2009

Попробуйте шаблон проектирования Observer. Ссылка включает образец кода на данный момент.

Главное, чего вам не хватает, - это то, что вы можете передать ссылку без нарушения инкапсуляции, если эта ссылка приведена как интерфейс (абстрактный класс), который реализует ваш объект.

person Brian    schedule 03.02.2009
comment
Итак, вы говорите, что я должен передать ссылку на свой сетевой интерфейс, и это достойное решение, если это просто абстрактный класс? Я посмотрел на шаблон наблюдателя в Википедии, но не уверен, что понимаю, как он будет работать с сигналами. Фактически, похоже, что сигналы уже реализуют это. - person drby; 03.02.2009
comment
Нет. Сетевому интерфейсу даются ссылки на графический интерфейс, а не наоборот. Кроме того, эти ссылки являются абстрактными и предоставляют доступ только к функции-члену события. - person Brian; 03.02.2009

Поскольку это общий вопрос, я постараюсь ответить на него, хотя я «всего лишь» программист на Java. :)

Я предпочитаю использовать интерфейсы (абстрактные классы или любой другой соответствующий механизм в C ++) на обеих сторонах моих программ. С одной стороны - ядро ​​программы, содержащее бизнес-логику. Он может генерировать события, например Классы GUI могут получать, например (для вашего примера) «stringReceived.» С другой стороны, ядро ​​реализует интерфейс «слушателя пользовательского интерфейса», который содержит такие методы, как «stringEntered».

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

[Edit] В стартовом классе для моих приложений почти всегда есть такой код:

Core core = new Core(); /* Core implements GUIListener */
GUI gui = new GUI(); /* GUI implements CoreListener */
core.addCoreListener(gui);
gui.addGUIListener(core);

[/Редактировать]

person Bombe    schedule 03.02.2009
comment
Разве Core не должен реализовывать CoreListener, а графический интерфейс - GUIListener? Если нет, то мне не хотелось бы поддерживать ваш код: P - person Gordon Thompson; 03.02.2009
comment
Вовсе нет, потому что ядро ​​должно реагировать на события графического интерфейса пользователя, а графический интерфейс должен реагировать на события ядра. И поддерживать это очень просто, потому что мне не нужно заботиться о графическом интерфейсе в ядре (и наоборот). - person Bombe; 03.02.2009

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

Я бы поставил контроллер, который управляет как EntryField, так и CLI: каждый раз, когда EntryField изменяется, вызывается CLI, всем этим управляет контроллер.

person mouviciel    schedule 03.02.2009

Вы можете отделить ЛЮБОЙ графический интерфейс и легко обмениваться сообщениями с помощью шаблонных виртуальных пакетов. Также ознакомьтесь с этим проектом.

person Vanilla Face    schedule 12.06.2015