По каким причинам вы бы использовали отдельное расширение класса для каждого делегата в Swift?

13

Я работал над учебником Рэя Вендерлиха и заметил, что автор использует расширения класса для хранения обратных вызовов делегата, а не для обработки их в самом классе, то есть:

делегировать обратные вызовы внутри расширения класса:

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

вместо того, чтобы содержать его в классе:

делегировать обратные вызовы внутри класса:

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

Я нашел это странным и интересным одновременно. У него есть файл, выделенный только для расширений в классе LogsViewController с именем «LogsViewControllerExtension.swift», и имеет разные расширения для каждого протокола делегата: UITableViewDataSource, UISplitViewDelegate и т. Д., Т.е.

несколько расширений классов каждый с делегатами обратных вызовов в своем собственном файле:

extension LogsViewController: UISplitViewControllerDelegate {
    ... callbacks
}

extension LogsViewController : UIPopoverPresentationControllerDelegate {
    ... callbacks
}

Почему?

Какие преимущества есть в этом? Я могу видеть, где это может быть немного более читабельным, чтобы отделить это, но в то же время это уровень косвенности. Существуют ли ОО-принципы, которые поддерживают или против этого?

morbidhawk
источник
1
Вы можете написать много таких структур, которые в основном поддерживаются принципами ОО, но все же виновны в перепроектировании.
Роберт Харви
1
@RobertHarvey true, но вы подразумеваете, что приведенный здесь пример кода является формой переобучения? Делегирование является распространенным паттерном в разработке iOS. Кроме того, независимо от того, используете ли вы расширения класса или нет, код внутри него не изменяется, так что это больше похоже на реструктуризацию кода, а не на повторный инжиниринг
morbidhawk
1
Я вижу эту схему добавления нескольких расширений в один и тот же файл, и я не знаю, откуда это происходит. Кажется, некоторые люди уже злоупотребляют этим и просто произвольно бросают каждый маленький кусочек кода в расширение в том же файле. Я уверен, что есть веская причина для этого, но у меня создается впечатление, что некоторые программисты просто делают это, не понимая, почему. Я продолжаю искать преимущество этого по сравнению с использованием MARK: - и хотел бы найти какой-то определенный источник того, почему расширения должны использоваться таким образом.
Дэвид Лари

Ответы:

15

Я не знаю, почему вы сказали, что это добавляет уровень косвенности. Может быть, вы подразумеваете нечто иное, чем традиционное значение, потому что при этом не возникает никакой дополнительной косвенности. Но зачем это делать?

Я делаю это, потому что это более модульно. Весь код, который требуется интерфейсу, сгруппирован в одном месте (за исключением фактических свойств.) Если позже я решу создать отдельный класс для реализации этого протокола (и, таким образом, ввести фактический уровень косвенности), все, что мне нужно do - изменить расширение на собственный класс (пропуская необходимые свойства через функцию init) и создать свойство в ViewController для создания экземпляра объекта.

Я также поместил в расширение любые частные функции, которые используются только функциями этого протокола. Я не зашел так далеко, чтобы создать совершенно отдельный файл для расширения, но благодаря этому становится ясно, что эти частные функции предназначены только для этого протокола.

И, во всяком случае, люди часто жалуются на жирные контроллеры представления, и разбиение контроллера представления таким образом помогает поддерживать его более организованным, даже если это на самом деле не делает контроллер представления более тонким.

Даниэль Т.
источник
Я понимаю, что вы подразумеваете под модульностью. Как только я понял, что происходит, это выглядело как чистый способ разделения проблем. Уровень косвенности, я думаю, для нового набора глаз (и я пристрастен, идущий от пути Obj-c). Я чувствую тот же уровень косвенности при создании подклассов, где ссылается код, который определен где-то еще в базовом классе, и его немного сложнее найти. Я согласен, что организационно это добавляет выгоды (с небольшой косвенной стоимостью)
morbidhawk
Я думаю, что это было сделано ранее с категориями классов в Obj-C. Я никогда не был большим их поклонником, но я думаю, что они требовали отдельного файла. Так как теперь в Swift вы можете сохранить расширение в том же файле, что я, вероятно, сделаю, если решу разбить их таким образом
morbidhawk
2

Как сказал Даниил в отношении косвенности, его уровня нет.
Я согласен с ним, и я хотел бы добавить дополнительную мощную функцию расширений протокола, которую я знал недавно.

Скажем, у вас есть протокол, didCopyTextнапример. Вы будете реализовывать это как:

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

В Swift свойства и методы не реализованы в объявлении протокола, вы бы хотели записать реализацию в каждом классе, соответствующем didCopyText, с инкрементным числом классов, соответствующих этому протоколу с той же реализацией, это закончилось бы просто беспорядком повторный код. Вот где протокольные расширения пригодятся.

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

С реализацией свойств протокола и методов. Теперь любой класс, соответствующий этому протоколу, будет использовать ту же реализацию.

MEnnabah
источник
спасибо, что поделились этим. В то время, когда я задавал этот вопрос, я не осознавал некоторые недостатки наследования ОО, и то, что вы описываете здесь, является отличным способом повторного использования одних и тех же функций всеми реализующими классами, которые соответствуют этому интерфейсу, вместо того, чтобы заставлять наследовать все от объекта. И если вы следуете принципу разделения интерфейса, вы можете по мере необходимости разбивать протоколы, чтобы у реализующих классов никогда не было свойств / методов от интерфейса, который им не нужен.
morbidhawk
1

Допустим, ваш класс поддерживает три протокола, и поэтому вам нужно добавить три набора функций. Единственная цель этих функций - поддержка протокола, поэтому вам нужна документация.

Однако, если вы добавляете расширение для каждого протокола, и в каждом расширении вы реализуете именно те функции, которые необходимы для этого протокола, это делает ваш код намного более читабельным.

Я бы, скорее всего, не поместил их в отдельные файлы, если бы эти расширения не были действительно большими.

gnasher729
источник