Search code examples
swiftinheritanceprotocols

Design Pattern problem: When Extensions and Protocols fail


With Protocols, you define behaviour with stubs of functionality; but no code.

With Extensions, you add code and behaviours to every single type of the class you are extending, within the scope of your project; even if some of those should not need or be able to know about such code.

Multiple-Inheritence doesn't exist in Swift.

So what do you do, when you have a subset of all your ViewControllers, who all need to know how to do the same thing, and you don't want to type the code out again and again or copy/paste, and yet Extensions mean the larger set of ViewControllers get code they should never need or know about?

Let's say I have 10 View Controllers in my small app.

9 of them have multiple UITextField's for data entry.

Of that 9, 5 of them have data entry requirements that are partially common between all 5, but not common across all of them. e.g. 3 of the 5 might require a field for 'Age', but not all 5. 4 of them might require 'ID Number' but the 5th doesn't.

So, from the 5 ViewControllers, there are 12 separate data entry fields, that are shared amongst them, but not on each of them.

For all of these 12 data entry fields (UITextFields) code can be written that limits and formats the data as it's entered into the UITextField, preventing incorrect data i.e. Age over 100, or ID Number > number of employees etc.

The question is this:

It would be easy to create an Extension for UIViewController that held all 12 data entry field validation code checks - and that is indeed what I've done.

However, as these fields only appear on 5 out of the 10 View Controllers in my app, all 10 View Controllers have this extension.

So how do I limit this to just the 5 that need it?

I don't want the code in a class, because then each one would have to instantiate their own, and it's a duplication in memory.

Same for using a struct.

The only solution I can think of is a singleton; but I always shy away from singletons because I'm never sure if it's justified to use one.


Solution

  • A protocol extension will do what you're looking for. Using your example you can have something like this:

    protocol ageValidatable {
        func isValidAge(_ age: Int) -> Bool
    }
    
    extension ageValidatable {
        func isValidAge(_ age: Int) -> Bool {
            return age > 0 && age < 150
        }
    }
    

    Now any ViewController that implements ageValidatable will have a default implementation of isValidAge without needing to define it. You can

    • Override it if you choose to
    • Give this only to the ViewControllers that require it
    • Allow a ViewController to conform to more than one of these protocols
    • Create typealiases for groups of these protocols if you end up conforming to a lot of them. i.e. typealias employeeValidatable = ageValidatable & employeeIDValidatable