jeudi 14 octobre 2021

Swift POP - Base class vs Protocol for abstract methods

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.

Aucun commentaire:

Enregistrer un commentaire