Переопределяющее свойство с универсальным типом универсального базового класса

#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. Принудительное разворачивание