Swift - metoda klasy, która musi zostać przesłonięta przez podklasę


91

Czy istnieje standardowy sposób tworzenia „czystej funkcji wirtualnej” w języku Swift, tj. taki, który musi być nadpisany przez każdą podklasę, a jeśli tak nie jest, powoduje błąd czasu kompilacji?


Możesz to zaimplementować w superklasie i zrobić asercję. Widziałem to używane w Obj-C, Javie i Pythonie.
— David Skrundz

8
@NSArray To powoduje błąd w czasie wykonywania, a nie kompilacji
— JuJoDi

Ta odpowiedź też ci pomoże. wprowadź opis linku tutaj
— Chamath Jeevan

Czysta funkcja wirtualna jest implementowana przez protocols (w porównaniu do interfaces w Javie) .Jeśli potrzebujesz ich używać jak metod abstrakcyjnych, spójrz na to pytanie / odpowiedź: stackoverflow.com/a/39038828/2435872
— jboi

Odpowiedzi:


153

Masz dwie możliwości:

1. Użyj protokołu

Zdefiniuj nadklasę jako protokół zamiast klasy

Pro : Sprawdzanie czasu kompilacji, czy każda „podklasa” (a nie rzeczywista podklasa) implementuje wymagane metody

Wada : „Nadklasa” (protokół) nie może implementować metod ani właściwości

2. Assert w super wersji metody

Przykład:

class SuperClass {
    func someFunc() {
        fatalError("Must Override")
    }
}

class Subclass : SuperClass {
    override func someFunc() {
    }
}

Pro : Potrafi implementować metody i właściwości w nadklasie

Wada : brak kontroli czasu kompilacji


3
@jewirth nadal nie dostaniesz sprawdzenia czasu kompilacji podklas
— drewag

6
Protokół nie może implementować metod, ale zamiast tego można je udostępnić za pomocą metod rozszerzających.
— David Moles

2
Od wersji Swift 2.0 są teraz również rozszerzenia protokołów :) Dokumentacja Apple .
— Ephemera

4
Chociaż fatalErrornie zapewnia sprawdzania czasu kompilacji, fajnie jest, że kompilator jest przynajmniej wystarczająco inteligentny, aby nie wymagać od ciebie dostarczania wartości zwracanej dla metody, gdy wywoływana jest ścieżka wykonania fatalError.
— bugloaf

3
Przypadek 2: Pamiętaj, że jeśli wywołasz super.someFunc()metodę z nadpisaniem, otrzymasz błąd pomimo tego, że go przesłoniłeś. Wiesz, że nie powinieneś tego nazywać, ale ktoś inny nie musi o tym wiedzieć i po prostu postępować zgodnie ze standardową praktyką.
— Jakub Truhlář

50

Poniższe pozwala na dziedziczenie z klasy, a także na sprawdzenie czasu kompilacji protokołu :)

protocol ViewControllerProtocol {
    func setupViews()
    func setupConstraints()
}

typealias ViewController = ViewControllerClass & ViewControllerProtocol

class ViewControllerClass : UIViewController {

    override func viewDidLoad() {
        self.setup()
    }

    func setup() {
        guard let controller = self as? ViewController else {
            return
        }

        controller.setupViews()
        controller.setupConstraints()
    }

    //.... and implement methods related to UIViewController at will

}

class SubClass : ViewController {

    //-- in case these aren't here... an error will be presented
    func setupViews() { ... }
    func setupConstraints() { ... }

}

2
miło, typalias na ratunek :)
— Chris Allinson

Jakikolwiek sposób, aby uniemożliwić użytkownikom tego interfejsu API wyprowadzanie ich klas clild z ViewControllerClass zamiast z ViewController? Jest to dla mnie świetne rozwiązanie, ponieważ za kilka lat zacznę pochodzić od mojego aliasu typu i zapomnę o tym, jakie funkcje należy do tego czasu zastąpić.
— David Rector

@David Rector, czy możesz ustawić swoją klasę jako prywatną, a alias typu publicznego? Przepraszam, wysyłam wiadomość z mojego telefonu, nie mogę się sprawdzić.
— ScottyBlades

1
Idealne rozwiązanie, dziękuję za to. Jak podkreślił @DavidRector, byłoby wspaniale, gdyby istniało rozwiązanie, które sprawi, że tylko alias typu był publiczny, ale niestety nie wydaje się to możliwe.
— CyberDandy

35

Nie ma wsparcia dla abstrakcyjnych klas / funkcji wirtualnych, ale prawdopodobnie w większości przypadków możesz użyć protokołu:

protocol SomeProtocol {
    func someMethod()
}

class SomeClass: SomeProtocol {
    func someMethod() {}
}

Jeśli SomeClass nie zaimplementuje someMethod, zostanie wyświetlony następujący błąd kompilacji:

error: type 'SomeClass' does not conform to protocol 'SomeProtocol'

30
Zauważ, że działa to tylko dla najwyższej klasy, która implementuje protokół. Każda podklasa może beztrosko zignorować wymagania protokołu.
— memmons

2
Ponadto używanie
— typów

14

Innym obejściem, jeśli nie masz zbyt wielu "wirtualnych" metod, jest przekazanie podklasy "implementacji" do konstruktora klasy bazowej jako obiektów funkcji:

class MyVirtual {

    // 'Implementation' provided by subclass
    let fooImpl: (() -> String)

    // Delegates to 'implementation' provided by subclass
    func foo() -> String {
        return fooImpl()
    }

    init(fooImpl: (() -> String)) {
        self.fooImpl = fooImpl
    }
}

class MyImpl: MyVirtual {

    // 'Implementation' for super.foo()
    func myFoo() -> String {
        return "I am foo"
    }

    init() {
        // pass the 'implementation' to the superclass
        super.init(myFoo)
    }
}

1
nie tak przydatne, jeśli masz kilka bardziej wirtualnych metod
— Bushra Shahid

@ xs2bush Jeśli więcej twoich metod jest wirtualnych niż nie, prawdopodobnie lepiej będzie zadeklarować je w protokole i udostępnić "niewirtualne" metodami rozszerzającymi.
— David Moles

1
to jest dokładnie to, co ostatecznie
— zrobiłem

1

Można użyć protokołu vs twierdzeniu, jak zasugerowano w odpowiedzi tutaj przez drewag. Brakuje jednak np. Protokołu. Kryję się tutaj,

Protokół

protocol SomeProtocol {
    func someMethod()
}

class SomeClass: SomeProtocol {
    func someMethod() {}
}

Teraz wszystkie podklasy są wymagane do implementacji protokołu sprawdzanego w czasie kompilacji. Jeśli SomeClass nie zaimplementuje someMethod, zostanie wyświetlony następujący błąd kompilacji:

błąd: typ „SomeClass” nie jest zgodny z protokołem „SomeProtocol”

Uwaga: działa to tylko dla najwyższej klasy, która implementuje protokół. Każda podklasa może beztrosko zignorować wymagania protokołu. - tak skomentował przezmemmons

Twierdzenie

class SuperClass {
    func someFunc() {
        fatalError("Must Override")
    }
}

class Subclass : SuperClass {
    override func someFunc() {
    }
}

Jednak asercja będzie działać tylko w czasie wykonywania.


-2

Będąc nowym w rozwoju iOS, nie jestem do końca pewien, kiedy to zostało zaimplementowane, ale jednym ze sposobów uzyskania najlepszego z obu światów jest zaimplementowanie rozszerzenia dla protokołu:

protocol ThingsToDo {
    func doThingOne()
}

extension ThingsToDo {
    func doThingTwo() { /* Define code here */}
}

class Person: ThingsToDo {
    func doThingOne() {
        // Already defined in extension
        doThingTwo()
        // Rest of code
    }
}

To rozszerzenie pozwala ci mieć domyślną wartość dla funkcji, podczas gdy funkcja w zwykłym protokole nadal dostarcza błąd czasu kompilacji, jeśli nie jest zdefiniowany


1
abstrakcyjne funkcje są przeciwieństwem domyślnych implementacji
— Hogdotmac
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.