Home > Enterprise >  Swift POP - Base class vs Protocol for abstract methods
Swift POP - Base class vs Protocol for abstract methods

Time:10-14

I have this code in C# and want to model it in Swift. Since there is no abstract class/function in Swift and Swift is protocol oriented, this pattern becomes invalid. So I want to know best practice for this case.

abstract class Bird
{
  string name
  {
    get;
    set;
  }

  public abstract string family ();

  public void print ()
  {
    Console.WriteLine ($ "Family: {family()} | Name: {name}");
  }
}

class Eagle:Bird
{
  public override string family ()
  {
    return "Accipitridae";
  }
}

class Turaco:Bird
{
  public override string family ()
  {
    return "Musophagidae";
  }
}

I have done some research in StackOverflow, read medium posts and here are the solutions I have found. Now the question is which solution is in "swifty" way and doesn't seem odd?

  1. Just throw an error from abstract methods in base class:
class Bird {
...
    func family() -> String {
        fatalError("Not implemented") 
    }
}
  1. Move all abstract methods to a protocol, keep an instance of that protocol in Base class, conform child to the protocol and pass self to base as the instance of the protocol:
protocol BirdDelegate: AnyObject { // Or maybe not delegate? BirdProtocol???
    func family() -> String
}

class Bird {
    ...

    weak var delegate: BirdDelegate?
    
    func print() {
        guard let delegate = delegate else {
            return
        }
        
        print("Family: \(delegate.family()) | Name: \(name)");
    }
}

class Eagle: Bird, BirdDelegate {
    override init() {
        super.init()
        delegate = self
    }
    
    func family() -> String {
        return "Accipitridae";
    }
}
  1. Move all abstract methods to a protocol, conform child to the protocol and try to cast the base to that protocol:
protocol BirdDelegate: AnyObject { // Or maybe not delegate? BirdProtocol???
    func family() -> String
}

class Bird {
    ...
    
    func print() {
        guard let selfDelegate = self as? BirdDelegate else {
            return
        }
        
        print("Family: \(delegate.family()) | Name: \(name)");
    }
}

class Eagle: Bird, BirdDelegate {
    func family() -> String {
        return "Accipitridae";
    }
}
  1. Make bird protocol:
protocol Bird {
    var name: String {get set}
    func family() -> String
    func print() -> Void
}

extension Bird: {
    func print() {
        ...
    }
}

4.1. To avoid declaring all stored properties in each child class, a base class can be created:

class BirdBase {
    var name: String = ""
}

class Eagle: BirdBase, Bird {
    ...
}

Some comments about solutions

It would be nice to have all errors at compile time.

    1. Children are not forced to implement the family() function and the error will appear at runtime. Besides that throwing an "unimplemented" error doesn't sound good to me.
    1. Children are forced to implement all functions of the protocol but since the protocol instance in Base class is optional, children can extend the Base class and not pass the delegate which will lead to a runtime error or a situation where print() function is useless (this solution is similar to UITableView, UITableViewDatSource, but I'm not sure it is perfect for this case). Same for (3.).
    1. Everything is safe, because errors are known at compile time. The downside of this solution when you are working with classes is declaring all stored properties in each child class.
  • 4.1. We have BaseBird class which doesn't have family() property. Seems like it's incomplete and useless class.

CodePudding user response:

I think inheritance is better than protocols in this scenario.

class Bird {
    let family: String
    init(family: String) {
        self.family = family
    }
    
    func printFamily() {
        print("name: \(family)")
    }
}


class Eagle: Bird {
    
    override init(family: String) {
        super.init(family: family)
    }
    
    override func printFamily() {
        // super runs the print in the bird class
        super.printFamily()
        // Do whatever you want if you want to add something else
        print("\(family) does also have beaks")
    }
}
let eagleClass = Eagle(family: "Accipitridae")

eagleClass.printFamily()

CodePudding user response:

You can do something like this: Declare a protocol Bird and provide a default implementation of a print method (which can be overridden if needed). Then you define a specific struct (Eagle, Pigeon, etc.) that provides a family as a computable string (or as a constant at the initialisation time).

protocol Bird {

    var name: String { get set }
    var family: String { get }

    func print()
}

extension Bird {

    func print() {
        Swift.print("Family: \(family) | Name: \(name)")
    }
}

struct Eagle: Bird {

    var name: String

    var family: String {
        "Accipitridae"
    }
}

let myPet = Eagle(name: "Buddy")
myPet.print()

You can also achieve something similar with a base class:

class BaseBird: Bird {

    var name: String
    let family: String

    init(name: String, family: String) {

        self.name = name
        self.family = family
        assert(type(of: self) != BaseBird.self, "Use a subclass of \(BaseBird.self)")
    }
}

final class Eagle: BaseBird {

    init(name: String) {
        super.init(name: name, family: "Accipitridae")
    }
}

let myPet = Eagle(name: "Buddy")
myPet.print()
  • Related