Расширение протокола для реализации другого поведения

Быстрый вопрос, скажем, например, у вас есть протокол Bark:

protocol MakeSound {
   func bark()
}

Суперкласс Dog, реализующий лай и плавание:

class Dog: MakeSound {
}

Затем различные типы собак, которые расширяют это:

class Poodle: Dog {
}

class GermanShephard: Dog {
}

class SheepDog: Dog {
}

Но пудели тявкают, они не лают… все собаки лают, просто они делают это по-разному… Как я могу дать им определенное поведение Лая?

Продлить протокол?...

extension MakeSound {
    func bark()
    func yapper()
}

Но тогда у пуделей и немецких овчарок есть оба поведения (тявкающая немецкая овчарка?!)

Если я сделаю 2 расширения и проверю класс типов, используя where Self = ?

extension MakeSound where Self: GermanShephard {
    func bark() {
        print("Bark")
    }
}

extension MakeSound where Self: Poodle{
    func yapper() {
        print("yap yap")
    }
}

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

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


person Ben Smith    schedule 15.12.2016    source источник
comment
Каким будет ваш подход к этому в Java? Это было бы точно так же, как в Swift.   -  person Alexander    schedule 16.12.2016


Ответы (5)


Если я правильно понял ваш вопрос, может быть, это хорошо поможет.

вы можете дать bark() реализацию по умолчанию, расширив протокол. Затем в других классах, соответствующих протоколу, вы можете изменить реализацию функции коры:

protocol Bark {
   func bark()
}

//default implementation
extension Bark {
      func bark() { print("Bark") }
}

class Dog: Bark {}

//By calling bark func in Poodle, you change the default implementation.
class Poodle: Dog {
   func bark() { print("Yap") }
}

class GermanShephard: Dog {
     func bark() { print("Woof") }
}

let dog = Dog()
let poodle = Poodle()
let germanShephard = GermanShephard()

dog.bark()
//Bark
poodle.bark()
//Yap
germanShephard.bark()
//Woof

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

Изменить после комментария:

Это одна из основных причин, по которой протоколы полезны. Они устраняют тесную связь, связанную с созданием подклассов. Это базовый пример, так как вся тема содержит много информации, но вместо создания подкласса Dog вы можете сделать протокол DogRepresentable и назначить все свойства и функции по умолчанию, которые все собаки реализуют одинаково. Затем вы можете расширить DogRepresentable, где self: UIViewController и реализовать функциональность по умолчанию:

protocol Barkable {
    func bark()
}

protocol DogRepresentable: Barkable {
//properties and functions all dogs will have with same implementation
}

extension DogRepresentable where Self: UIViewController {
//default implementation for functions all dogs will use
}

Назначая Barkable классу DogRepresentable, вы знаете, что любой класс, соответствующий DogRepresentable, также должен соответствовать Barkable.

Теперь, когда вы назначаете DogRepresentable классу, он получит всю реализацию по умолчанию, которую получит базовый класс, и вам придется вызывать функцию bark(), чтобы правильно соответствовать протоколу:

class Dog: DogRepresentable {
   func bark() { print("Bark") }
} 

class Poodle: DogRepresentable {
   func bark() { print("Yap") }
}

 class GermanShephard: DogRepresentable {
    //Won't conform because it doesn't have bark()
}

Таким образом, вы получите всю реализацию по умолчанию, такую ​​как базовый класс, но не забудете переопределить функцию суперкласса.

Изменить 2 на основе второго комментария:

В этом случае вам лучше всего оставить DogRepresentable без соответствия Barkable, а затем создать протоколы для разных типов: поэтому, если у вас есть собаки, которые тявкают, вы можете сделать протокол Yappable, который имеет функцию лая и реализацию по умолчанию. Тогда у вас может быть другой протокол Barkable с собственной функцией лая и собственной реализацией по умолчанию. Затем пусть класс соответствует тому протоколу, которому он должен соответствовать. Пудель соответствует Yappable, Собака соответствует Barkable.

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

person JustinM    schedule 15.12.2016
comment
Спасибо, я только что отредактировал вопрос выше, чтобы он был более ясным. Я думаю, ваше решение могло бы сработать... Но что, если Пудель забыл переопределить, а затем начал лаять? Могу ли я защититься от этого? - person Ben Smith; 15.12.2016
comment
Я имею в виду, например, если у меня много собак, которые тявкают. Я не хочу повторять код, везде переопределяя функцию коры. Мне нужна еще одна функция протокола в MakeSound для тявканья, но разрешить ее использовать только определенным типам собак. - person Ben Smith; 15.12.2016
comment
Твой отступ... артистичен? - person Alexander; 16.12.2016
comment
лол, второй раз ты меня сегодня поймал. Дай мне посмотреть, что я могу сделать. - person JustinM; 16.12.2016

Это идеальный вариант использования наследования протокола.

protocol DogSoundMaker {
    func makeSound()
}

protocol Barker: DogSoundMaker {}
protocol Yapper: DogSoundMaker {}
protocol Woofer: DogSoundMaker {}

extension Barker {
    func makeSound() { print("Bark") }
}

extension Yapper {
    func makeSound() { print("Yap yap, I am a glorified rodent") }
}

extension Woofer {
    func makeSound() { print("Woof") }
}

struct Poodle: Yapper {}
struct GermanShephard: Barker {}
struct SheepDog: Woofer {}

Poodle().makeSound()
GermanShephard().makeSound()
SheepDog().makeSound()
person Alexander    schedule 15.12.2016

Это ответ, протокол makeSound реализуется двумя разными протоколами звуков лая dogYap и dogBark. Которые, в свою очередь, расширяют и реализуют различные звуки для лая и тявканья. Затем классы различных типов собак (которые расширяют класс dog) могут реализовывать dogYap или dogBark, в зависимости от того, какой звук издает эта собака.

protocol MakeSound {
    func makeSound()
    var canBark: Bool { get }
    var canYap: Bool { get }
}

protocol dogBark: MakeSound {
    func makeSound()
}

protocol dogYap: MakeSound {
    func makeSound()
}

extension dogYap {
    func makeSound() {
        print("Yap")
    }
}

extension dogBark {
    func makeSound() {
        print("bark")
    }
}

extension MakeSound {
    func makeSound() {}
    var canBark: Bool { return self is dogBark }
    var canYap: Bool { return self is dogYap }

}

class Dog {
    var age: Int?
    var colour: UIColor?

}

class Poodle: Dog, dogYap {
}

class GermanShephard: Dog, dogBark  {
}

class SheepDog: Dog, dogBark {
}

//German shephard and Belgian bark in the same way
let germanShep = GermanShephard()
germanShep.makeSound()
germanShep.canBark
germanShep.canYap


let sheepDog = SheepDog()
sheepDog.makeSound()
sheepDog.canBark
sheepDog.canYap

let poodle = Poodle()
poodle.makeSound()
poodle.canBark
poodle.canYap
person Ben Smith    schedule 15.12.2016
comment
Я бы отказался от такого дизайна. Базовые типы не должны иметь информацию о своих производных типах. Такой подход делает базовый тип не расширяемым. - person Alexander; 16.12.2016
comment
Вы имеете в виду, что базовый тип Собака, имеющий возраст и цвет, является информацией об их базовых типах? В основном из вашего ответа выше (кстати, спасибо), и здесь я вижу, что вы вообще не использовали наследование. Где тогда хранить возраст и окрас каждой собаки? - person Ben Smith; 16.12.2016
comment
Нет, я говорю о том, как протокол MakeSound знает и зависит от своих производных протоколов, dogBark и dogYap. Этот дизайн не позволяет мне, например, расширить MakeSound до dogWoof. - person Alexander; 16.12.2016
comment
Он знает и зависит от производных протоколов, потому что я использую для него расширение и определяю canBark/canYap как true, если реализованы производные протоколы (dogBark, dogYap)? Итак, я помещу эти свойства для canBark и canYap в мой базовый класс Dog? - person Ben Smith; 16.12.2016
comment
Это вопрос...? - person Alexander; 16.12.2016
comment
Да извини! Мой вопрос в основном заключался в том, куда должны идти свойства, которые я вставил в MakeSound, чтобы он не знал о своих производных протоколах? - person Ben Smith; 17.12.2016
comment
Эти переменные не нужны. По сути, вы используете эти 2 логических значения как часть системы псевдотипа времени выполнения. В этом нет необходимости. Смотри мой ответ - person Alexander; 17.12.2016

Я считаю, что это правильный способ реализации такого поведения:

import UIKit

protocol DogSoundMaker {}
protocol Barker: DogSoundMaker {}
protocol Yapper: DogSoundMaker {}

extension DogSoundMaker{
    var canBark: Bool { return self is Barker }
}
extension Barker {
    func makeSound() {
        print("Bark")
    }
}
extension Yapper {
    func makeSound() {
        print("Yap")
    }
}

class GermanShepherd: Barker {

}

class Poodle: Yapper{

}

class Chiwawa: Yapper {

}

var germanShep = GermanShepherd()
var poodleDog = Poodle()
poodleDog.makeSound()
poodleDog.canBark
germanShep.canBark
germanShep.makeSound()
person Ben Smith    schedule 24.05.2017

Пока родительский класс не помечен final, его функции могут быть переопределены.
Теперь, когда класс соответствует протоколу, вы в основном выполняете требования протокола, добавляя переменные/функции.

В вашем случае, поскольку класс Dog не помечен как final, вы можете просто переопределить функцию bark.
Ничего особенного не требуется, в конце концов, это просто функция

Пример:

protocol Barkable {
    func bark()
}

class Dog: Barkable {
    func bark() {
        print("Dog barks")
    }
}

class Poodle: Dog {
    override func bark() {
        print("Poodle yaps")
    }
}

class GermanShephard: Dog {
    override func bark() {
        print("GermanShephard barks")
    }
}

class SheepDog: Dog {
    override func bark() {
        print("SheepDog barks")
    }
}

Dog().bark()
Poodle().bark()
GermanShephard().bark()
SheepDog().bark()
person staticVoidMan    schedule 21.02.2019