Swift, классы на основе расширенного протокола не соответствуют исходному протоколу
Эти протоколы дают мне кошмары.
Я пытаюсь реализовать пару протоколов и соответствующих им классов, чтобы у меня могли быть реализации по умолчанию, но настраиваемые реализации доступны путем расширения протоколов / классов. Пока что вот что у меня есть:
protocol ProtA {
var aProperty: String { get set }
var anotherProperty:String { get set }
func aFunc (anArgument: String) -> String
}
protocol ProtB: ProtA {
var aThirdProperty: String { get set }
}
protocol ProtC {
func doSomething(parameter: Int, with anotherParameter: ProtA)
}
class ClassA: ProtA {
var aProperty: String = "Hello, I'm a String."
var anotherProperty: String = "I'm not the same String."
func aFunc (anArgument: String) -> String {
return anArgument
}
}
class ClassB: ProtB {
var aProperty: String = "Hello, I'm a String."
var anotherProperty: String = "I'm not the same String."
var aThirdProperty: String = "I'm yet another String!"
func aFunc (anArgument: String) -> String {
return anArgument
}
}
class ClassC: ProtC {
func doSomething(parameter: Int, with anotherParameter: ProtA) {
print (anotherParameter.aProperty) // Works fine.
}
}
Тогда, если я сделаю
class ClassC: ProtC {
func doSomething(parameter: Int, with anotherParameter: ProtA) {
print (anotherParameter.aProperty) // Works fine.
}
}
Но если я сделаю
class ClassD: ProtC {
func doSomething(parameter: Int, with anotherParameter: ProtA) {
print (anotherParameter.aThirdProperty) // Value of type 'ProtA' has no member 'aThirdProperty'
}
}
и, если вместо этого я
class ClassE: ProtC {
func doSomething(parameter: Int, with anotherParameter: ProtB) {
print (anotherParameter.aThirdProperty) // Type 'ClassE' does not conform to protocol 'ProtC'
}
}
Что я делаю неправильно?
2 ответа
Там нет ничего плохого. Вы должны просто убедиться, что объявления семантически согласованы. Вы должны либо создать ProtD, объявляющий метод с параметром ProtB, либо развернуть полученный параметр ParamA, чтобы использовать его в качестве ProtB.
func doSomething(parameter: Int, with anotherParameter: ProtB) {
if let a = anotherParameter as? ProtA {
print (a.aThirdProperty)
}
}
Эта проблема
При наследовании от типа нельзя сузить типы параметров, используемых в переопределенных функциях. Это то, что вы сделали, изменив параметр от типа ProtA
(более общий тип), чтобы ProtB
(более конкретный тип).
Это является следствием принципа подстановки Лискова. Проще говоря, подкласс должен иметь возможность делать (как минимум) все, что может делать суперкласс.
ProtC
устанавливает, что все соответствующие типы имеют функцию func doSomething(parameter: Int, with anotherParameter: ProtA)
с типом (Int, ProtA) -> Void)
,
Ваша измененная функция в ClassE
имеет тип (Int, ProtB) -> Void
, Однако эта функция больше не может заменять ту, которую она переопределяет.
Предположим, что можно было сделать то, что вы пытались. Посмотрите, что произойдет:
let instanceConformingToProtA: ProtA = ClassA()
let instanceConformingToProtC: ProtC = ClassE()
// This would have to be possible:
instanceConformingToProtC(parameter: 0, amotherParameter: instanceConformingToProtA)
Но, ClassE()
не может взять instanceConformingToProtA
в качестве действительного аргумента для второго параметра, потому что это ProtA
не требуется ProtB
,
Решение
Решение этой проблемы полностью зависит от того, чего вы пытаетесь достичь. Мне понадобится дополнительная информация, прежде чем я смогу продолжить.
Как правило, при переопределении наследуемых элементов:
- Типы параметров должны быть одинаковыми или более общими.
- Например, вы не можете переопределить функцию с параметром типа
Car
и измените тип параметра наRaceCar
, Это нарушает способность ваших классов работать сRaceCar
с, что он должен быть в состоянии сделать LSP. - Например, вы можете переопределить функцию с параметром типа
Car
и измените параметр наVehicle
, Это сохраняет способность ваших классов работать с `Транспортными средствами.
- Например, вы не можете переопределить функцию с параметром типа
- Типы возврата должны быть одинаковыми или более конкретными.
- Например, вы не можете переопределить функцию с типом возврата
Car
с функцией, которая возвращаетVehicle
, Это означало бы, что возвращаемое значение "менее мощно", чем гарантирует суперкласс. - Например, вы можете переопределить функцию с типом возврата
Car
с функцией, которая возвращаетRaceCar
, Это будет означать, что возвращаемое значение будет "более мощным", и это будет, по крайней мере, так же, как гарантии суперкласса.
- Например, вы не можете переопределить функцию с типом возврата