Как сделать протокол, который объявляет свойство типа протокола?

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

Если я объявлю два протокола, которые определяют Shape следующим образом:

protocol Shape {
  var sides : Int { get }
  var fill : Fill { get }
}

protocol Fill {
  var color : UIColor { get }
}  

Теперь, чтобы реализовать это, я определяю две структуры: одну для квадрата, а другую для сплошной заливки. Как это:

struct SolidFill : Fill {
  var color : UIColor
}

struct Square : Shape {
  var sides : Int = 4
  var fill : SolidFill = SolidFill(color: UIColor.blackColor())
}

Я получаю сообщение об ошибке компиляции "Тип "Квадрат" не соответствует протоколу "Форма"". Если я заставлю тип заполнения быть заполненным как var fill : Fill, ошибка компиляции исчезнет. Почему я не могу указать Square для заливки более конкретный тип, чем позволяет протокол?

Я понимаю, что для этого конкретного примера я мог бы изменить вещи, чтобы не использовать этот шаблон, используя Enum для заполнения или переделывая что-то еще. Я просто хочу знать, почему я не могу использовать тип fill, соответствующий протоколу.


person Ryan    schedule 10.11.2015    source источник


Ответы (2)


Потому что это не имело бы смысла.

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

Таким образом, для протокола Fill имеет смысл иметь другие протоколы, которые унаследованы от него, такие как SolidFill и, возможно, StripedFill.

Давайте добавим к вашему примеру протокол StripedFill. Теперь давайте посмотрим на вашу структуру Square и выясним, почему она не реализует протокол Shape, который частично требует наличия свойства Fill.

struct Square : Shape {
  var sides : Int = 4
  var fill : SolidFill = SolidFill(color: UIColor.blackColor())
}

Единственное, что мы можем присвоить свойству fill Square, это то, что реализует протокол SolidFill. Но протокол Shape требует, чтобы наша фигура могла присваивать свойству fill все, что соответствует протоколу Fill. И в случае, когда у нас также есть протокол с именем StripedProtocol, который наследуется от протокола Fill, он будет включать объекты, реализующие этот протокол (независимо от того, реализуют ли они также протокол SolidFill).

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

Однако вы можете сделать следующее:

struct Square: Shape {
    var sides: Int = 4
    var fill: Fill = SolidFill(color: UIColor.blackColor())
}

Таким образом, в этом случае мы по-прежнему определенно присваиваем SolidFill нашему Square, но мы по-прежнему допускаем, чтобы свойство fill соответствовало тому, что оно определено в протоколе, и разрешаем назначать братьям и сестрам SolidFill свойство fill.

person nhgrif    schedule 10.11.2015

Только с геттером теоретически все может быть в порядке, но если бы был сеттер, он определенно не соответствовал бы протоколу, поскольку кто-то может попытаться установить другой объект, который соответствует протоколу.

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

Вы можете сделать это:

struct Square : Shape {
  var sides : Int = 4
  var fill : Fill { solidFill }
  private var solidFill = SolidFill(color: UIColor.blackColor())
}
person Joseph Lord    schedule 10.11.2015