Chandan kumar
Chandan kumar

Reputation: 1084

Protocols inheritance Swift

I am a novice in swift protocol and try to use it by considering Solid principles. I have taken two protocols (A, B) and combined them with the third protocol (D). Idea is to choose between two protocol from another class based on need. Check the code to understand more.

protocol A {
    func fetchA()
}

protocol B {
    func fetchB()
}

protocol D : A,B { }

extension D {
    func fetchB() { }
    func fetchA() {}
}

class B1 : D {
    func fetchB() {
        print("B")
    }
}

class A1 : D {
    func fetchA() {
        print("A")
    }
}

protocol C {
    func fetchC()
}


class C1 : C {
    func fetchC() {
        print("C")
    }
}

enum Ab {
    case a
    case b
}

struct Hello  {
    let first : D
    let second : C
    init(first : D , second :C) {
        self.first = first
        self.second = second
    }
    
    func show(type:Ab){
        switch type {
        case .a:
            first.fetchA()
        case .b:
            first.fetchB()
        }
        second.fetchC()
    }
}

let obj = Hello.init(first: A1(), second: C1())
obj.show(type:.a)

So current code print "A". Now if I can change first parameter to B1() and type .b and it prints "B". I want to improve the code base and remove the enum type and want to get the same result with help of protocol. What changes need to be done here.? Thanks in advance.

Concrete Goal : I have NetworkManger(Class A1), FirebaseManger(Class B1), and LocalDatabaseManger(Class C1). I want to do a network call either with NetworkManger or FirebaseManger and if it fails call LocalDatabaseManger.

Upvotes: 2

Views: 1323

Answers (2)

Rob Napier
Rob Napier

Reputation: 299275

Based on your "Concrete Goal" paragraph, I believe you currently have something like this:

class FirebaseManager {
    func fetchWithFirebase() -> Bool {
        print("Firebase")
        return true
    }
}

class NetworkManager {
    func fetchFromNetwork() -> Bool {
        print("Network")
        return true
    }
}

class LocalDatabaseManager {
    func fetchFromDatabase() -> Bool {
        print("Local")
        return true
    }
}

There are three classes that don't share any particular interface, but all can do the same thing in roughly the same way. And you want some client to use the primary, and if it can't then to use the backup:

class Client {
    let primary: FirebaseManager
    let backup: LocalDatabaseManager
    init(primary: FirebaseManager, backup: LocalDatabaseManager) {
        self.primary = primary
        self.backup = backup
    }

    func show() -> Bool {
        return primary.fetchWithFirebase() || backup.fetchFromDatabase()
    }
}

But now FirebaseManger and LocalDatabaseManger are hard-coded and more importantly their different APIs are hard-coded. So how to fix that?

This is the point that protocols come in. Client needs something that can fetch, a Fetcher:

protocol Fetcher {
    func fetch() -> Bool
}

If that existed, then you could write Client this way:

class Client {
    var sources: [Fetcher]
    init(sources: [Fetcher]) {
        self.sources = sources
    }

    func show() -> Bool {
        for source in sources {
            if source.fetch() { return true }
        }
        return false
    }
}

You don't even have to limit yourself to just two sources. You could have a whole list and try one after the other. That's nice, but none of your Managers actually conform to Fetcher.

This is where the power of Swift's protocols comes to light. You can retroactively conform types to protocols. You don't even have to control the original type. You can do this anywhere.

extension FirebaseManager: Fetcher {
    func fetch() -> Bool { fetchWithFirebase() }
}

Now FirebaseManager conforms to Fetcher, and you can pass it to client. And you can conform the rest of your types and pass them to your Client:

extension NetworkManager: Fetcher {
    func fetch() -> Bool { fetchFromNetwork() }
}

extension LocalDatabaseManager: Fetcher {
    func fetch() -> Bool { fetchFromDatabase() }
}

let obj = Client(sources: [FirebaseManager(), LocalDatabaseManager()])
obj.show()
// Firebase

No need for inheritance at all. Just create a protocol that represents the behaviors that your algorithm (show()) needs, and then extend each of your types to perform those behaviors.

Upvotes: 4

Sweeper
Sweeper

Reputation: 270980

One way is adding an extra requirement when conforming to D, that being you specify which method you want to use.

protocol D : A,B {
    func fetch()
}

class B1 : D {
    func fetchB() {
        print("B")
    }
    
    func fetch() {
        fetchB()
    }
}

class A1 : D {
    func fetchA() {
        print("A")
    }
    
    func fetch() {
        fetchA()
    }
}

struct Hello  {
    let first : D
    let second : C
    
    func show(){
        first.fetch()
        second.fetchC()
    }
}

let obj = Hello.init(first: A1(), second: C1())
obj.show()

But this might not be the best way, depending on what these protocols and classes actually are supposed to represent.

Upvotes: 1

Related Questions