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:

  1. 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.
  2. 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.

With the detail hidden inside the 'module', and just one public property exposed, there's different ways to implement the 'arrows' in that diagrams'

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.