Is it OK to make my GUI depend on a concrete controller class
directly? Why/why not?
No: Because loose coupling is fundamental to good design. Depend on abstractions. Depending on implementations makes it impossible to substitute one implementation for another without recompiling the dependent at the very least. Tightly coupling any two classes impedes future flexibility.
Yes: It's a judgement call. If loose coupling definitely has no future benefit and is too expensive to justify, couple away.
But really try not to. Having the GUI depend on the controller is a valid design decision. But always depend on abstractions, never implementations. Come on... you knew that. I mean, okay, if nobody ever wants to switch out the controller, then you haven't gained anything by using an interface. Except peace of mind, knowing you wrote tidier, less-coupled code. And arguably easier to test.
As for the choice of how the communication between GUI and controller occur, it's a toss-up. You could use events, but wiring them up is a drag and they won't cross app boundaries. But by using events you may feel you have the loosest possible coupling. GUI and controller never need to address (abstractions of) each other except to initially wire up the events. That might feel nice. Or you could use method calls against interfaces, which may feel like a slightly tighter coupling, but it's hardly different really. Any way you do it, X has to know things about Y for them to communicate.
Circular dependencies between GUI and controller are okay (on abstractions!). There are many variations of the MVC pattern. Every time I use it, I mold it to my needs/mood. That said, I do try to avoid circular dependencies. I prefer to limit dependencies to one direction. Or none at all!
For example in my current project the controllers know about the views, but the views have absolutely no idea that the controllers exit. The controllers susbcribe to the views' events, and pass data back to them via a single state object which the view binds to. The state class is the only thing the view depends on. And the only thing the controller knows about the view is it's interface and the type of its state object. And those things are defined in an external module so that the view module can be removed completely and the controller will still compile, and vice-versa. This is a very loose coupling. Almost none at all (both sides depend on a third module instead).
Some people do it the other way 'round.
Reasons to avoid concrete dependencies
- difficult to swap one implementaton for another - dependent will likely need modification or at least recompilation
- harder to maintain - can't modify components independently of each other
- harder to test in isolation - need to modify and recompile dependent to swap in a mock implementation of dependency