Aus welchen Gründen würden Sie eine separate Klassenerweiterung für jeden Delegierten in Swift verwenden?

13

Ich habe ein Ray Wenderlich-Tutorial durchgearbeitet und festgestellt, dass der Autor Klassenerweiterungen verwendet, um Rückrufe von Stellvertretern zu speichern, anstatt sie in der Klasse selbst behandeln zu lassen, dh:

Rückrufe innerhalb der Klassenerweiterung delegieren:

extension LogsViewController : UIPopoverPresentationControllerDelegate {
    func adaptivePresentationStyleForPresentationController(controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
        ...
    }
}

im Gegensatz dazu, dass es in der Klasse enthalten ist:

Rückrufe innerhalb der Klasse delegieren:

class LogsViewController : UITableViewController, UIPopoverPresentationControllerDelegate {
    func adaptivePresentationStyleForPresentationController(controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
        ...
    }
}

Ich fand das seltsam und interessant zugleich. Er hat eine Datei, die nur den Erweiterungen in der LogsViewController-Klasse namens "LogsViewControllerExtension.swift" gewidmet ist, und hat für jedes Delegate-Protokoll eine andere Erweiterung: UITableViewDataSource, UISplitViewDelegate usw. Dh:

mehrere Klassenerweiterungen, die jeder mit Rückrufen in seiner eigenen Datei delegiert:

extension LogsViewController: UISplitViewControllerDelegate {
    ... callbacks
}

extension LogsViewController : UIPopoverPresentationControllerDelegate {
    ... callbacks
}

Warum?

Welche Vorteile bringt dies? Ich kann sehen, wo es vielleicht ein bisschen lesbarer ist, dies zu trennen, aber gleichzeitig ist es eine Ebene der Indirektion. Gibt es OO-Prinzipien, die dies unterstützen oder ablehnen?

Morbidhawk
quelle
1
Sie können eine Menge solcher Strukturen schreiben, die von den OO-Grundsätzen grundsätzlich unterstützt werden, sich jedoch der Überentwicklung schuldig machen.
Robert Harvey
1
@RobertHarvey Stimmt, aber implizieren Sie, dass der hier gezeigte Beispielcode eine Form der Überentwicklung ist? Delegation ist ein gängiges Muster in der iOS-Entwicklung. Unabhängig davon, ob Sie Klassenerweiterungen verwenden oder nicht, ändert sich der darin enthaltene Code nicht, sodass dies eher einer Code-Umstrukturierung gleichkommt als einem Re (/ over) -Engineering
morbidhawk
1
Ich sehe dieses Muster, eine Reihe von Erweiterungen in dieselbe Datei zu werfen, und ich weiß nicht, woher das kommt. Es scheint, dass einige Leute es bereits missbrauchen und einfach jedes kleine Stück Code willkürlich in eine Erweiterung innerhalb derselben Datei werfen. Ich bin mir sicher, dass es einen guten Grund gibt, dies gelegentlich zu tun, aber ich habe den Eindruck, dass einige Programmierer dies nur tun, ohne zu verstehen, warum. Ich bin ständig auf der Suche nach einem Vorteil gegenüber MARK: - und würde gerne eine definitive Quelle dafür finden, warum Erweiterungen auf diese Weise verwendet werden sollten.
David Lari

Antworten:

15

Ich weiß nicht, warum Sie sagten, es füge eine Indirektionsebene hinzu. Vielleicht meinen Sie damit etwas anderes als die traditionelle Bedeutung, weil hierdurch keine zusätzliche Indirektion erzeugt wird. Aber warum?

Ich mache es, weil es modularer ist. Der gesamte Code, der für die Schnittstelle erforderlich ist, wird an einer einzigen Stelle gruppiert (mit Ausnahme der tatsächlichen Eigenschaften). Wenn ich später eine separate Klasse zum Implementieren dieses Protokolls auswähle (und somit eine tatsächliche Indirektionsebene einführe), muss ich nur noch alles tun Ändern Sie die Erweiterung in eine eigene Klasse (übergeben Sie die erforderlichen Eigenschaften über eine init-Funktion), und erstellen Sie im ViewController eine Eigenschaft, in die das Objekt instanziiert werden soll.

Ich habe auch alle privaten Funktionen, die nur von den Funktionen dieses Protokolls verwendet werden, in die Erweiterung eingefügt. Ich bin nicht so weit gegangen, eine vollständig separate Datei für die Erweiterung zu erstellen, aber dies macht deutlich, dass diese privaten Funktionen nur für dieses Protokoll bestimmt sind.

Und auf jeden Fall beschweren sich die Leute oft über fette View-Controller, und diese Aufteilung hilft dabei, die Organisation zu verbessern, auch wenn der View-Controller dadurch nicht dünner wird.

Daniel T.
quelle
Ich verstehe, was Sie unter Modularität verstehen. Als ich begriff, was los war, sah es nach einem sauberen Weg aus, die Bedenken zu trennen. Ich denke, die Indirektionsebene ist für eine neue Gruppe von Augen (und ich bin voreingenommen, wenn ich vom Objekt-C-Weg komme). Ich empfinde die gleiche Indirektionsebene beim Unterklassen, in der auf Code verwiesen wird, der an einer anderen Stelle in der Basisklasse definiert ist und der etwas schwieriger zu finden ist. Ich bin damit einverstanden, dass es organisatorisch Vorteile bringt (mit ein wenig indirekten Kosten)
morbidhawk
Ich vermute, dass dies schon mit Klassenkategorien in Obj-C gemacht wurde. Ich war nie ein großer Fan von denen, aber ich denke, sie benötigten eine separate Datei. Da Sie jetzt in Swift die Erweiterung in derselben Datei behalten können, werde ich das wahrscheinlich tun, wenn ich mich entscheide, sie so aufzubrechen
morbidhawk
2

Wie Daniel in Bezug auf die Indirektion sagte, gibt es kein Niveau davon.
Ich stimme ihm zu und möchte eine zusätzliche leistungsstarke Funktion für Protokollerweiterungen hinzufügen, die ich kürzlich kannte.

Angenommen, Sie haben beispielsweise ein Protokoll didCopyText. Sie werden das implementieren als:

protocol didCopyText {
  var charachtersCount: Int {get}
  func addToClipboardWith(text: String)
}

In Swift sind Eigenschaften und Methoden nicht in der Protokolldeklaration implementiert. Sie möchten die Implementierung in jede Klasse schreiben, die didCopyTextmit der übereinstimmt. Bei einer inkrementellen Anzahl von Klassen, die mit diesem Protokoll mit derselben Implementierung übereinstimmen, würde dies nur zu einem Durcheinander führen wiederholter Code. Hier bieten sich Protocol Extensions an.

protocol didCopyText {
var charachtersCount: Int {
    get {
     // implementation
    }
}
func addToClipboardWith(text: String) {
      // implementation
 }
}

Mit der Implementierung der Eigenschaften und Methoden des Protokolls. Jede Klasse, die diesem Protokoll entspricht, verwendet dieselbe Implementierung.

MEnnabah
quelle
Vielen Dank für das Teilen. Zu der Zeit, als ich diese Frage stellte, war mir nicht klar, welche Nachteile die OO-Vererbung hat. Was Sie hier beschreiben, ist eine großartige Möglichkeit, die gleichen Funktionen wiederzuverwenden, die von allen implementierenden Klassen verwendet werden, die dieser Schnittstelle entsprechen, anstatt dazu gezwungen zu werden alles von einem Objekt erben. Wenn Sie dem Prinzip der Schnittstellensegregation folgen, können Sie die Protokolle nach Bedarf aufteilen, um sicherzustellen, dass die implementierenden Klassen niemals Eigenschaften / Methoden der Schnittstelle haben, die sie nicht benötigen.
Morbidhawk
1

Angenommen, Ihre Klasse unterstützt drei Protokolle, und deshalb müssen Sie drei Funktionssätze hinzufügen. Der einzige Zweck dieser Funktionen besteht in der Unterstützung eines Protokolls. Sie benötigen daher eine Dokumentation.

Wenn Sie jedoch für jedes Protokoll eine Erweiterung hinzufügen und in jeder Erweiterung genau die Funktionen implementieren, die für dieses eine Protokoll erforderlich sind, wird der Code viel besser lesbar.

Ich würde sie höchstwahrscheinlich nicht in separaten Dateien ablegen, es sei denn, diese Erweiterungen sind wirklich groß.

gnasher729
quelle