Разница между применением протокола + расширения VS с использованием экземпляра класса

Я пытался понять протокольно-ориентированное программирование, но не понимаю разницы между двумя следующими сценариями...

Сценарий 1 У меня есть два класса UIViewControllers. Оба этих класса должны использовать некоторые общие функции, поэтому я создаю протокол и расширение с реализацией протокола по умолчанию, а затем контроллерам представления нужно только иметь протокол в строке класса, и они автоматически наследуют необходимые функции. то есть...

protocol SomeProtocol {
    func foo()
}
extension SomeProtocol {
    func foo(){
        //execute
    }
}

class FirstViewController: UIViewController, SomeProtocol {
    ...

    func doSomething(){
        foo()
    }
}

class SecondViewController: UIViewController, SomeProtocol {
     ...

    func doSomethingElse(){
        foo()
    }
}

Сценарий 2 У меня есть два класса UIViewControllers. Оба этих класса должны использовать некоторые общие функции, поэтому я создаю класс контроллера, и оба класса UIViewController используют экземпляр класса контроллера. то есть...

class FirstViewController: UIViewController {
    ...
    let controller = Controller()

    func doSomething(){
        controller.foo()
    }
}

class SecondViewController: UIViewController {
    ...
    let controller = Controller()

    func doSomethingElse(){
        controller.foo()
    }
}


class Controller {
    func foo(){
        //execute...
    }
}`

Так в чем же разница? Везде, где мне нужно использовать функцию foo(), я могу просто взять экземпляр Controller(). Какое преимущество я получаю, помещая функцию foo() в протокол, а затем наследуя классы, которым требуется foo(), от протокола?


person MikeG    schedule 08.09.2017    source источник
comment
Заданный вопрос, похоже, не имеет ничего общего с протокольно-ориентированным программированием...   -  person matt    schedule 08.09.2017


Ответы (4)


Есть несколько подходов, когда вы хотите добавить эту функциональность foo к нескольким подклассам UIViewController (или что у вас есть):

  1. Протокольный подход с расширениями:

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

    Достоинство этого подхода (когда вы можете) заключается в том, что вы начинаете пользоваться преимуществами, описанными в WWDC 2015 Протокольно-ориентированное программирование в Swift.

  2. Компонентный подход (где у вас есть экземпляр Controller, который могут продавать ваши контроллеры представления):

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

Для полноты картины есть еще несколько вариантов повторного использования:

  1. Как предложил Мэтт, вы также можете реализовать foo как расширение некоторого общего базового класса.

    Это прекрасно работает, когда подпрограмма имеет смысл не только для двух ваших существующих подклассов, но и для всех подклассов. Но это не подходит, если эта функциональность уникальна только для этих двух конкретных подклассов.

  2. Вы также можете поместить foo в подкласс этого базового класса (например, FooViewController подклассов UIViewController), а затем два предыдущих подкласса создать подкласс нового класса FooViewController.

    Это устраняет неизбирательный характер простого расширения базового класса.

    Проблема в том, что это не так гибко, как первые два подхода (например, отсутствие множественного наследования, один набор Fooable методов и другой набор Barable методов).

Суть в том, что правильный подход зависит от конкретной проблемы, которую вы пытаетесь решить. Ваш пример слишком общий, чтобы мы могли дать конкретный совет.

person Rob    schedule 08.09.2017

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

Допустим, у вас есть контроллер класса с тремя функциями.

Class Controller {

  func foo() { }

  func bar() { }

  func fooBar() { }
}

Теперь у вас есть ситуация, когда вам нужен другой объект, которому нужна только функциональность foo(), однако с подклассом Controller у вас теперь есть все 3 функции. Или вам нужно скопировать func foo() код, чтобы иметь функциональность в обоих классах.

 class SomeViewController: UIViewController {
  /// You only need `func foo()` in this class, but now you have to use an object that carries all the extra functionality you don't need. 
  let controller: ControllerSubclass
}

Чтобы ограничить эти ограничения и сделать ваш код более читаемым/понятным, вы создаете протокол Fooing и назначаете тип Fooing.

 protocol Fooing {
   func foo()
 }

 class SomeViewController: UIViewController {

   let controller: Fooing
 }

Внеся это изменение, вы:

1 - Укажите в своем коде, что вы ожидаете, что этот объект сделает одну вещь, которую он может сделать, вызовет функцию foo().

2. Вы не ограничены классом Controller, вы можете использовать любой объект, который соответствует Fooing, обеспечивая гораздо большую гибкость.

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

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

person JustinM    schedule 08.09.2017

Какое преимущество я получаю, помещая функцию foo() в протокол, а затем наследуя классы, которым требуется foo(), от протокола?

Если вы используете протоколы, вы сможете хранить экземпляр общего SomeProtocol, не заботясь о том, что это такое на самом деле:

var myFooable: SomeProtocol

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

С другой стороны, использование экземпляров Controller — плохая идея. Любой класс может создать новый объект Controller и вызвать для него методы. То, что вы пытаетесь сделать здесь, - это выразить свойство «может», что и делает «соответствие протоколу». Если вы используете экземпляр Controller, это будет выражать отношение «имеет».

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

person Sweeper    schedule 08.09.2017

поэтому я создаю класс контроллера, и оба класса UIViewController используют экземпляр класса контроллера.

Проблема с этой идеей заключается в следующем: как бы вы это сделали, скажем, для UITableViewController? Вы не можете, потому что вы не можете превратить свой класс контроллера в суперкласс UITableViewController.

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

person matt    schedule 08.09.2017
comment
но что, если мне не нужно, чтобы каждый UIViewController имел эту функциональность, а только несколько? - person MikeG; 09.09.2017
comment
Я не понимаю. UIViewControllers имеют много функций, которые вам уже не нужны. Все встроенные классы работают. Какая вам разница, если у них есть еще больше? Если вам не нужен метод, вы его не вызываете. - person matt; 09.09.2017