I'm attempting to formalize the model / logic / view controller pattern used in my apps by creating base classes to be used by all implementations. These are classes due to the shared storage and internal API that comes along with many of these types, but protocol abstractions would be considered if they made anything easier. These formalizations include two types: one to represent the observable state being offered by the model / logic controller, and another to represent the action. We start with a simple base class (simplified).
class ModelController<State, Action> { }
This is then refined by the LogicController
type, as logic controllers may offer additional functionality. However, our generics start to get less ergonomic, as we have to define additional generic types, we can't just inherit those defined by ModelController
.
class LogicController<State, Action>: ModelController<State, Action> {}
Obviously this seems rather redundant, but I understand the limitation here: classes can only inherit from concrete classes, so we must provide a fully defined superclass. I'm not sure whether this is an inherent limitation of inheritance or a Swift limitation, but it makes these relations ships a bit awkward.
The ergonomic issue gets worse when we want to define our concrete implementations of these controllers:
final class TestModelController: ModelController<TestModelController.State, TestModelController.Action> {
struct State {}
enum Action {}
}
Yikes. The subtypes not being visible when defining the inheritance clause is a bit annoying, as is the fact that types with the same names as the generics aren't picked up automatically, so they must be fully qualified.
However, the real issue comes when trying to implement a base view controller that uses a single type of logic controller.
class LogicalViewController<State, Action, Brain: LogicController<State, Action>>: UIViewController {}
Not only do we have to declare the Brain
type we actually care about but the same placeholders we've defined twice before. Now, this wouldn't be so bad if the definitions we had to actually use wasn't so redundant:
final class TestViewController: LogicalViewController<TestLogicController.State, TestLogicController.Action, TestLogicController> {}
As far as I can tell, there's no way to define this inheritance in a way to allows the compiler to see that the State
and Action
types I defined for the view controller should automatically come from the TestLogicController
type.
Now, this isn't too bad overall since we only define the inheritance once for each controller, and it's a pretty powerful set of abstractions so I'm willing to pay that complexity, but surely there's a better way here? I've tried replacing some of the inheritance with protocols but ran into the same issues: having to create overly generic concrete types to satisfy the associatedtype
requirements.
Anyone have better suggestions for a design with the same capabilities?