2

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.

5
  • Why does A1 even conform to B (as a result of conforming to D) in the first place? It feels like it shouldn't. Commented Apr 2, 2021 at 13:26
  • Can you explain in more concrete terms what you're trying to accomplish? It looks like you're trying to wrap several objects into one and sort of mimic multiple inheritance, but why? Commented Apr 2, 2021 at 13:26
  • 1
    SOLID is a set of principles for OOP. Swift protocols are not object-oriented tools. They are different approach to composition that is not based on inheritance. While some concepts of SOLID make sense with protocols, I believe what you're trying to do here is reinvent class inheritance using protocols, which is an anti-goal of Swift. It favors composition rather than inheritance and static rather than dynamic dispatch. Commented Apr 2, 2021 at 13:39
  • To develop good protocols, start with concrete implementations, and then use protocols to extract shared behaviors that you can build algorithms on top of. Starting with a complex hierarchy of protocols is almost always the path to trouble. Commented Apr 2, 2021 at 13:40
  • @Caleb I have update the question. Commented Apr 2, 2021 at 13:41

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you so much for your detailed explanation.
You might not even need the protocol, just change client’s sources to be an array of functions.
That will work for very simple cases like this one where the functions have exactly the same API, just different names. But a protocol quickly becomes more convenient if adapting is ever more complicated than that. (You can still do it with closures, but the syntax gets messier.)
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.