Protocol Proxy

I’ve found a tricky situation and I can’t see an elegant way to solve it. Basically, I have a protocol with multiple functions. Then I have multiple implementations, and I would like to have an object that implements the protocol by choosing one of these implementations depending on the function to call:

protocol DataSource {
    func loadUsers(groupId: String) -> Users
    func loadData(id: String) -> Data
}

struct NetworkDataSource: DataSource {
    func loadUsers(groupId: String) -> Users // Network call
    func loadData(id: String) -> Data // Network call
}

struct StaticDataSource: DataSource {
    func loadUsers(groupId: String) -> Users // Return static data
    func loadData(id: String) -> Data // Return static data
}

struct AppDataSource: DataSource {
    func loadUsers(groupId: String) -> Users // Call NetworkDataSource
    func loadData(id: String) -> Data // Call StaticDataSource
}

I could just implement the protocol manually:

struct AppDataSource: DataSource {
    func loadUsers(groupId: String) -> Users {
        return NetworkDataSource().loadUsers(groupId: groupId) }
    func loadData(id: String) -> Data {
        return StaticDataSource(id: id)
    }
}

The problem with this is that it’s mostly boilerplate code. The functions are just redirecting to another object which implements the protocol, without touching the parameters. If I need to change a function type, I need to change this redirection too. This is what I would like to do:

struct AppDataSource: DataSource {
    func loadUsers: DataSource.loadUsers = NetworkDataSource().loadUsers
    func loadData: DataSource.loadData = StaticDataSource().loadData
}

This doesn’t work right now. What would be a good way to approach this?

Thanks

This related to OO Patterns like Strategy, Factory, Facade, and AbstractFactory: polymorphism… I think implementing it would go better with classes rather than structs.

I believe the issue you have here is the fact that functions can’t be assigned. However, you may be able to get away with computed instance variables which can be:

protocol DataSource {
    var loadUsers: (String) -> Users { get }
    var loadData: (String) -> Data { get }
}

struct NetworkDataSource: DataSource {
    var loadUsers: (String) -> Users = … // Network call
    var loadData: (String) -> Data = … // Network call
}

struct StaticDataSource: DataSource {
    var loadUsers: (String) -> Users = … // Return static data
    var loadData: (String) -> Data = … // Return static data
}

struct AppDataSource: DataSource {
    var loadUsers: (String) -> Users = NetworkDataSource().loadUsers // Call NetworkDataSource
    var loadData: (String) -> Data = StaticDataSource().loadData // Call StaticDataSource
}

Unfortunately, you’d need to drop the argument labels in this case.

Yes, that is the closest solution I have found, but loosing argument labels is kind of a dealbreaker here. I’m not sure why we can’t have argument labels with functions as variables.

See the alternatives considered for SE-0111.

That is still waiting for the Core team to comment on their announcement that labels would be back someday as the issue gets reconsidered. I think it was really bad jay we lost argument labels in closures and functions stored on variables…

I've since relaxed my stance on argument labels, and I'm using now the "struct with functions as properties" approach. It gives a lot of flexibility. Also, argument labels can be added if really needed by adding a function to the struct that just calls the corresponding stored function.

Terms of Service

Privacy Policy

Cookie Policy