Хранение GUI отдельно - PullRequest
       6

Хранение GUI отдельно

4 голосов
/ 03 февраля 2009

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

GUI -> MainWindow -> CommandLineInterface -> EntryField

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

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

А в ctor:

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. Это действительно общий вопрос.

Ответы [ 6 ]

8 голосов
/ 03 февраля 2009

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

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

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

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

3 голосов
/ 03 февраля 2009

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

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

2 голосов
/ 03 февраля 2009

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

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

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

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

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

2 голосов
/ 03 февраля 2009

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

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

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

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

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

[/ Edit]

0 голосов
/ 12 июня 2015

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

0 голосов
/ 03 февраля 2009

По моему мнению, CLI должен быть независимым от GUI. В архитектуре MVC она должна играть роль модели.

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

...