#swift #generics #inheritance
#swift #общие #наследование
Вопрос:
Вот что я хочу сделать:
protocol GenericFactory {
associatedtype Input
associatedtype Value
func create(with input: Input) -> Value
}
class Base<Factory: GenericFactory> {
var input: Factory.Input {
preconditionFailure("To be overriden")
}
}
protocol ValueProtocol {}
struct SomeInputImpl {}
protocol ValueFactory: GenericFactory where Input == SomeInputImpl, Value: ValueProtocol {}
class Child<Factory: ValueFactory>: Base<Factory> {
override var input: Factory.Input {
return SomeInputImpl()
}
}
В строке swift override var input: Factory.Input {
я получаю сообщение об ошибке Property 'input' with type 'SomeInputImpl' cannot override a property with type 'Factory.Input'
. Я не понимаю причину этой ошибки, поскольку тип ввода описан в ValueFactory
протоколе, но по некоторым причинам swift интерпретирует Factory.Ввод в базовом и фабричном.ввод в дочернем как разные типы. Может кто-нибудь, пожалуйста, объяснить мне, что здесь не так?
Комментарии:
1. Это ограничение Swift 4.2, которое устранено в Swift 5. Приведенный выше код компилируется в последней бета-версии Xcode. Но я бы действительно хотел поговорить с вами о том, что вы пытаетесь здесь создать. Это почти наверняка излишне сложно, и может быть решено гораздо проще другими способами. Как учитель, я пытаюсь понять, почему разработчики идут по этому пути, и этот вопрос — именно тот, который я искал для конкретного примера. Я был бы очень признателен, если бы вы связались со мной по поводу конкретной проблемы, которую вы пытаетесь решить на rob@neverwood.org или перейдя в чат здесь.
Ответ №1:
Просто для завершения вопроса о том, что это рассматривается в Swift 5, вот демонстрация, которая будет работать в Swift 5, но не в Swift 4.2, учитывая ваш пример кода.
struct Val: ValueProtocol {}
struct VF: ValueFactory {
func create(with input: SomeInputImpl) -> Val {
return Val()
}
}
let child = Child<VF>()
child.input // SomeInputImpl
Но вам почти наверняка было бы лучше использовать функции, а не GenericFactory
. Основная причина использования заводского шаблона заключается в работе с языками, в которых отсутствуют функции более высокого порядка и первоклассные типы, такие как Java до 8 версии. В языках с функциями более высокого порядка и первоклассными типами (такими как Swift) заводской шаблон обычно не нужен. Вы можете просто передать функцию (Input) -> Value
напрямую.
В вашем примере не отображается вызывающий объект, поэтому трудно точно определить, какое решение на основе функций будет работать лучше всего, но в большинстве случаев универсальные функции — это гораздо лучший подход, чем протоколы associatedType generics наследование. В частности, смешивание наследования класса с ассоциированными типами, как правило, заводит вас в тупик (поскольку ассоциированные типы и наследование классов являются несколько ортогональными подходами к полиморфизму).
Комментарии:
1. Спасибо, Роб, за твои ответы, они очень помогли мне переосмыслить некоторые части моего решения. Я бы согласился с вами по поводу использования protocols associatedType generics inheritance, если бы у swift не было ограничений в использовании только generics inheritance, таких как невозможность указать протокол в качестве конкретного типа для общего ограничения базового класса. Я напишу свое решение здесь немного позже, но если вы хотите поговорить более подробно, я буду рад.
2. Спасибо. На самом деле Swift надеется, что вы не используете наследование классов, а вместо этого просто используете протоколы. (Но это не значит, что они специально усложнили наследование классов или что вы не правы, расстраиваясь из-за них; генерики Swift просто еще не закончены.)
Ответ №2:
protocol ValueFactory: GenericFactory where Input: SomeInputImpl, Value: ValueProtocol {}
Входные данные должны подтверждать SomeInputImpl.
Поскольку SomeInputImpl имеет тип struct, он не может быть унаследован. Итак, вы должны сделать это классом или протоколом
class SomeInputImpl {}
Затем вы можете переопределить входную переменную
class Child<Factory: ValueFactory>: Base<Factory> {
override var input: Factory.Input {
return SomeInputImpl() as! Factory.Input
}
}
Комментарии:
1. Это именно то, как я это делал на данный момент, но это не то решение, которое я хочу, потому что здесь много проблем. 1. Общее ограничение «where Input: SomeInput» здесь не будет иметь значения, потому что вы можете возвращать все, что хотите, это будет просто работать 2. Если я верну экземпляр SomeInputImpl, а затем во время создания дочернего элемента определю Factory, который имеет другой тип ввода, он скомпилируется, но завершится сбоем при принудительном развертывании строки 3. Принудительное разворачивание