Search code examples
swiftprotocolsswift-protocols

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.


Solution

  • 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.