views:

601

answers:

5

I have a program that (amongst other things) has a command line interface that lets the user enter strings, which will then be sent over the network. The problem is that I'm not sure how to connect the events, which are generated deep inside the GUI, to the network interface. Suppose for instance that my GUI class hierarchy looks like this:

GUI -> MainWindow -> CommandLineInterface -> EntryField

Each GUI object holds some other GUI objects and everything is private. Now the entryField object generates an event/signal that a message has been entered. At the moment I'm passing the signal up the class hierarchy so the CLI class would look something like this:

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

And in the c'tor:

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

The passUp function just emits the signal again for the owning class (MainWindow) to connect to until I can finally do this in the main loop:

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

Now this seems like a real bad solution. Every time I add something to the GUI I have to wire it up all through the class hierarchy. I do see several ways around this. I could make all objects public, which would allow me to just do this in the main loop:

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

But that would go against the idea of encapsulation. I could also pass a reference to the network interface all over the GUI, but I would like to keep the GUI code as seperate as possible.

It feels like I'm missing something essential here. Is there a clean way to do this?

Note: I'm using GTK+/gtkmm/LibSigC++, but I'm not tagging it as such because I've had pretty much the same problem with Qt. It's really a general question.

+2  A: 

As this is a general question I’ll try to answer it even though I’m “only” a Java programmer. :)

I prefer to use interfaces (abstract classes or whatever the corresponding mechanism is in C++) on both sides of my programs. On one side there is the program core that contains the business logic. It can generate events that e.g. GUI classes can receive, e.g. (for your example) “stringReceived.” The core on the other hand implements a “UI listener” interface which contains methods like “stringEntered”.

This way the UI is completely decoupled from the business logic. By implementing the appropriate interfaces you can even introduce a network layer between your core and your UI.

[Edit] In the starter class for my applications there is almost always this kind of code:

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

[/Edit]

Bombe
Shouldn't Core implement CoreListener and GUI implement GUIListener ? If not then i'd hate to maintain your code :P
Gordon Carpenter-Thompson
Not at all because the core needs to response to GUI events, and the GUI needs to responde to core events. And maintaining that is very simple because I don’t have to care about the GUI in the core (and vice versa).
Bombe
+3  A: 

Try the Observer design pattern. Link includes sample code as of now.

The essential thing you are missing is that you can pass a reference without violating encapsulation if that reference is cast as an interface (abstract class) which your object implements.

Brian
So you're saying I should pass around a reference to my network interface, and that's a decent solution as long as it's just an abstract class? I looked at the observer pattern on wikipedia, but I'm not sure I see how that would work with signals. In fact it seems like signals already implement it.
drby
No. The network interface is being given references to the GUI, not the other way around. Also, those references are abstract and only give access to an event member function.
Brian
A: 

In my opinion, the CLI should be independant from GUI. In a MVC architecture, it should play the role of model.

I would put a controller which manages both EntryField and CLI: each time EntryField changes, CLI gets invoqued, all of this is managed by the controller.

mouviciel
+5  A: 

The root problem is that you're treating the GUI like its a monolithic application, only the gui is connected to the rest of the logic via a bigger wire than usual.

You need to re-think the way the GUI interacts with the back-end server. Generally this means your GUI becomes a stand-alone application that does almost nothing and talks to the server without any direct coupling between the internals of the GUI (ie your signals and events) and the server's processing logic. ie, when you click a button you may want it to perform some action, in which case you need to call the server, but nearly all the other events need to only change the state inside the GUI and do nothing to the server - not until you're ready, or the user wants some response, or you have enough idle time to make the calls in the background.

The trick is to define an interface for the server totally independently of the GUI. You should be able to change GUIs later without modifying the server at all.

This means you will not be able to have the events sent automatically, you'll need to wire them up manually.

gbjbaanb
Your are suggesting something very similar to MVC.
Ismael
I cliecked "Add Comments" with the internetion of mentioning MVC. That said, the Observer design pattern and MVC are close friends: Observer is used to allow the model to communicate with the view.
Brian
There's a lot of patterns that share the same principle. Decoupling things is generally a good thing.
gbjbaanb
+1  A: 

Short of having some global pub/sub hub, you aren't going to get away from passing something up or down the hierarchy. Even if you abstract the listener to a generic interface or a controller, you still have to attach the controller to the UI event somehow.

With a pub/sub hub you add another layer of indirection, but there's still a duplication - the entryField still says 'publish message ready event' and the listener/controller/network interface says 'listen for message ready event', so there's a common event ID that both sides need to know about, and if you're not going to hard-code that in two places then it needs to be passed into both files (though as global it's not passed as an argument; which in itself isn't any great advantage).

I've used all four approaches - direct coupling, controller, listener and pub-sub - and in each successor you loosen the coupling a bit, but you don't ever get away from having some duplication, even if it's only the id of the published event.

It really comes down to variance. If you find you need to switch to a different implementation of the interface, then abstracting the concrete interface as a controller is worthwhile. If you find you need to have other logic observing the state, change it to an observer. If you need to decouple it between processes, or want to plug into a more general architecture, pub/sub can work, but it introduces a form of global state, and isn't as amenable to compile-time checking.

But if you don't need to vary the parts of the system independently it's probably not worth worrying about.

Pete Kirkham