У меня есть FirstViewController, у которого есть некоторые IBOutlets, функции и ведущий. Затем у меня есть SecondViewController, который имеет все те же функции, что и FirstViewController, но с одной дополнительной функцией, IBOutlets и презентатор точно так же, как презентатор FirstViewController, но с одной дополнительной функцией.

Я читал об ООП и POP, и люди рекомендуют использовать POP.

Но мой вопрос: если в этой ситуации, когда мне нужен SecondViewController со всеми теми же функциями и IBOutlets, что и в FirstViewController, наследование не лучше протоколов?

Если я хочу использовать протокол, как это сделать, если я не хочу переписывать тот же код? С реализацией протоколов по умолчанию?

С наследованием

class FirstViewController: UIViewController {

 @IBOutlet weak var firstButton: UIButton!
 @IBOutlet weak var secondButton: UIButton!

...
...
 func firstFunction() {
  ...
 }

 func secondFunction() {
  ...
 }
}



class SecondViewController: FirstViewController {
    
     @IBOutlet weak var additionalButton: UIButton!
     
    ...
    ...
     func additionalFunction() {
      ...
     }
    }

С реализацией по умолчанию

protocol FirstViewControllerProtocols {
 func firstFunction()
 func secondFunction()
}

extension FirstViewControllerProtocols {

 func firstFunction() {
   print("do something")
 }

 func secondFunction() {
   print("do something")
 }
}

А потом

 class FirstViewController: UIViewController, FirstViewControllerProtocols {

   @IBOutlet weak var firstButton: UIButton!
   @IBOutlet weak var secondButton: UIButton!
 }



protocol SecondViewControllerProtocol {
 func thirdFunction()
}

extension SecondViewControllerProtocol {
 func thirdFunction() {
   print("do something new")
 }
}

 class SecondViewController: UIViewController, FirstViewControllerProtocols, SecondViewControllerProtocol {
  
  @IBOutlet weak var additionalButton: UIButton!
 
 }
 

Но с протокольным подходом я не могу наследовать iboutlets.

1
jackbob 14 Сен 2021 в 20:41

2 ответа

Лучший ответ

Ваш пример POP неверен. Дело в том, чтобы просто извлечь общий код в протоколы, а не дублировать вещи без надобности. Итак, FirstViewControllerProtocols имеет смысл, но для SecondViewControllerProtocol абсолютно нет оснований. Какой второй тип соответствует SecondViewControllerProtocol? Если вы избавитесь от этого, вы увидите, что большинство ваших проблем исчезнет.

Но не все из них. Вы также говорите о IBOutlets, которые представляют собой не только общий код. Если SecondViewController действительно является разновидностью FirstViewController (а не просто «случайно использует несколько кнопок»), то наследование может иметь смысл. Если дело в том, что есть несколько связанных IBOutlets, продублируйте IBOutlets, поскольку они связаны с типом. Это не по наследству.

Учитывая ваше описание, вы думаете в терминах: «У меня много кода, который может быть в некотором роде похож, и поэтому я должен сделать что-то, чтобы его абстрагировать». Это не подход; если они просто «похожи», то спросите себя, создаете ли вы абстракции без всякой цели. Если FirstViewController был изменен, изменится ли SecondViewController обязательно ? В противном случае они на самом деле не очень связаны. «СУХОЙ» («Не повторяйся») - это концепции, а не нажатия клавиш.

Подход таков: «У меня есть две вещи, которые принципиально похожи, со временем будут меняться аналогичным образом, и у меня есть алгоритмы, которые должны применяться к обоим, поэтому я должен извлечь протокол».

4
Rob Napier 14 Сен 2021 в 18:37

Создание подкласса одного класса контроллера представления из другого настраиваемого контроллера представления (а также использование реализаций по умолчанию в протоколах) на первый взгляд кажется таким элегантным, но в девяти случаях из десяти это ошибка. Если это абстрактный «контроллер базового представления», для некоторого общего поведения, который иногда может иметь некоторую полезность (хотя в зависимости от того, что это за общее поведение, часто есть лучшие шаблоны).

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

В настоящее время мы стремимся к тому, чтобы классы контроллеров представления были как можно более тонкими и тупыми. (См. публикации A Better MVC Дэйва Делонга или video. Это также один из многих мотивирующих факторов, лежащих в основе MVVM и связанных шаблонов.) Контроллеры представления предназначены исключительно для для настройки представлений и инициирования поведения при взаимодействии с пользователем или других системных событиях. Все, что выходит за рамки этого, обычно следует абстрагировать из контроллера представления. И если есть общие элементы пользовательского интерфейса в различных контроллерах представления, мы часто будем рассматривать включение контроллера представления, чтобы абстрагировать общий пользовательский интерфейс к третьему контроллеру представления, который становится потомком первых двух.

Поэтому вместо того, чтобы POP против ООП, спросите себя, принадлежит ли все это общее поведение какому-либо из этих двух контроллеров представления вообще. Если общее поведение - это различные методы делегата UIKit (например, источник данных таблицы, методы делегата представления коллекции и т. Д.), Это требует одного решения. Если это бизнес-логика, это требует другого решения. Если это служебные методы, еще один. Если это общий набор подпредставлений, опять же, еще один шаблон. Это просто зависит от обстоятельств.

2
Rob 14 Сен 2021 в 19:45