Быстро, как определить абстрактный класс и почему Apple изобрела связанный тип, но не использует общий протокол
Я быстрый новичок. Что-то озадачило меня при обучении. Теперь я хочу определить абстрактный класс или определить какой-то чисто виртуальный метод, но я не могу найти способ сделать это. У меня есть протокол со связанным типом (это также озадачило меня, почему бы не использовать общий протокол), и некоторые методы должны быть реализованы в базовом классе, а другие классы, унаследованные от базового класса, должны реализовывать другие методы в протоколе, Как я могу сделать? например:
Ptotocol P{
typealias TypeParam
func A()
func B()
}
class BaseClass<TypeParam> : P {
abstract func A()
func B(){
if someCondition {
A()
}
}
}
class ChildClass : BaseClass<Int> {
func A(){}
}
Это кажется очень странным, и я до сих пор не могу найти метод решения абстрактной проблемы.
1 ответ
У Swift есть нечто похожее: расширения протокола
Они могут определять реализации по умолчанию, поэтому вам не нужно объявлять метод в базовом классе, но это также не заставляет делать это в любом классе, структуре или перечислении.
protocol P {
associatedtype TypeParameter
func A()
func B()
}
extension P {
func A(){}
}
class BaseClass<TypeParam> : P {
typealias TypeParameter = TypeParam
func B(){
if someCondition {
A()
}
}
}
class ChildClass : BaseClass<Int> {
// implementation of A() is not forced since it has a default implementation
func A(){}
}
Другой подход заключается в использовании протокола вместо BaseClass
что больше соответствует протоколно-ориентированному программированию:
protocol Base {
associatedtype TypeParameter
func A()
func B()
}
extension Base {
func B(){
if someCondition {
A()
}
}
}
class ChildClass : Base {
typealias TypeParameter = Int
// implementation of A() is forced but B() is not forced
func A(){}
}
Однако одним из больших недостатков было бы то, что переменная типа протокола может использоваться только в общем коде (как общее ограничение):
var base: Base = ChildClass() // DISALLOWED in every scope
В качестве обходного пути для этого ограничения вы можете сделать тип оболочки:
// wrapper type
struct AnyBase<T>: Base {
typealias TypeParameter = T
let a: () -> ()
let b: () -> ()
init<B: Base>(_ base: B) where B.TypeParameter == T {
// methods are passed by reference and could lead to reference cycles
// below is a more sophisticated way to solve also this problem
a = base.A
b = base.B
}
func A() { a() }
func B() { b() }
}
// using the wrapper:
var base = AnyBase(ChildClass()) // is of type AnyBase<Int>
Что касается использования "настоящих" общих протоколов, команда Swift решила использовать associatedtype
потому что вы можете использовать много общих типов без необходимости записывать все в скобках <>
,
Например Collection
где у вас есть связанный Iterator
а также Index
тип. Это позволяет вам иметь конкретные итераторы (например, для Dictionary
а также Array
).
В общем, универсальные / ассоциированные типы хороши для оптимизации кода во время компиляции, но в то же время они иногда слишком статичны, когда вам придется использовать универсальный тип-обертку.
Полезная ссылка на некоторые шаблоны для работы со связанными типами.
(См. Также выше)
Более сложный способ решить проблему передачи методов по ссылке.
// same as `Base` but without any associated types
protocol _Base {
func A()
func B()
}
// used to store the concrete type
// or if possible let `Base` inherit from `_Base`
// (Note: `extension Base: _Base {}` is currently not possible)
struct BaseBox<B: Base>: _Base {
var base: B
init(_ b: B) { base = b}
func A() { base.A() }
func B() { base.B() }
}
struct AnyBase2<T>: Base {
typealias TypeParameter = T
var base: _Base
init<B: Base>(_ base: B) where B.TypeParameter == T {
self.base = BaseBox(base)
}
func A() { base.A() }
func B() { base.B() }
}
// using the wrapper:
var base2 = AnyBase2(ChildClass()) // is of type AnyBase2<Int>