Output Port Adapter for Single Point of Configuration in Complex UI
When you have nested and complex UI components with multiple sub-view controllers, passing an optional output port (event handler or delegate) down the tree is cumbersome.
There are two straight-forward approaches:
- Sub-components use the output port directly: When you inject the object reference, you may need to pass it down the whole view controller tree to reach all leaves.
- Sub-components are isolated from entry point: There’s one public interface for the port that is used by the module’s entry point, but internally, all sub-components have private wirings to communicate with the entry point.
The second option is “cleaner”, but for a two or three object component might also feel a bit too much.
A pattern I found useful is to inject a forwarding adapter early on as the actual port adapter, and replace its reference to the actual object at runtime:
- The output port that is exposed changes an internal reference inside the adapter, i.e. in one place only.
- The (private or internal) adapter itself is used as the actual output port of sub-components. It’s injected during setup eagerly once
public protocol WidgetEventHandler {
func manipulateWidget()
}
public class WidgetListViewController: NSViewController {
private final class WidgetEventHandlerForwarding: WidgetEventHandler {
/// Runtime-replaceable reference.
var base: WidgetEventHandler? = nil
func manipulateWidget(manipulateWidget) {
/// Forward, if possible.
base?.manipulateWidget()
}
}
private let eventHandlerForwarder = WidgetEventHandlerForwarding()
/// Public output port configuration API looks normal:
public var eventHandler: WidgetEventHandler? {
get { eventHandlerForwarder.base }
set { eventHandlerForwarder.base = newValue }
}
public override func awakeFromNib() {
super.awakeFromNib()
// ...
// Wire sub-components to the forwarder immediately:
self.detailViewControllerA.eventHandler = eventHandlerForwarder
self.detailViewControllerB.eventHandler = eventHandlerForwarder
self.detailViewControllerC.eventHandler = eventHandlerForwarder
}
}
Here, the forwarder implements the WidgetEventHandler
protocol and, well, forwards to base
if it’s set.
If you have a complex UI with multiple event handlers, delegates, data sources, and whatever else, you could opt for multiple forwarders – or lump them all together into one, if that makes sense. It can absolutely make sense, because “being the façade for various output ports” is a single concern, too. If the public facing component is complex but has a coherent data set, there shouldn’t be a lot of confusion.