Powiedzmy, że mam protokół:
public protocol Printable {
typealias T
func Print(val:T)
}
A oto realizacja
class Printer<T> : Printable {
func Print(val: T) {
println(val)
}
}
Spodziewałem się, że będę mógł używać Printable
zmiennej do drukowania takich wartości:
let p:Printable = Printer<Int>()
p.Print(67)
Kompilator skarży się z tym błędem:
„Protokół„ do druku ”może być używany tylko jako ogólne ograniczenie, ponieważ ma własne lub powiązane wymagania dotyczące typu"
Czy robię coś źle ? W każdym razie, aby to naprawić?
**EDIT :** Adding similar code that works in C#
public interface IPrintable<T>
{
void Print(T val);
}
public class Printer<T> : IPrintable<T>
{
public void Print(T val)
{
Console.WriteLine(val);
}
}
//.... inside Main
.....
IPrintable<int> p = new Printer<int>();
p.Print(67)
EDYCJA 2: Prawdziwy przykład tego, czego chcę. Zauważ, że to się nie skompiluje, ale przedstawia to, co chcę osiągnąć.
protocol Printable
{
func Print()
}
protocol CollectionType<T where T:Printable> : SequenceType
{
.....
/// here goes implementation
.....
}
public class Collection<T where T:Printable> : CollectionType<T>
{
......
}
let col:CollectionType<Int> = SomeFunctiionThatReturnsIntCollection()
for item in col {
item.Print()
}
Odpowiedzi:
Jak zauważa Thomas, możesz zadeklarować swoją zmienną, nie podając w ogóle typu (lub możesz jawnie podać typ jako typ
Printer<Int>
. Ale oto wyjaśnienie, dlaczego nie możesz mieć typuPrintable
protokołu.Nie można traktować protokołów z powiązanymi typami jak zwykłych protokołów i deklarować ich jako samodzielne typy zmiennych. Aby zastanowić się, dlaczego, rozważ ten scenariusz. Załóżmy, że zadeklarowałeś protokół do przechowywania dowolnego typu, a następnie przywracał go:
// a general protocol that allows for storing and retrieving // a specific type (as defined by a Stored typealias protocol StoringType { typealias Stored init(_ value: Stored) func getStored() -> Stored } // An implementation that stores Ints struct IntStorer: StoringType { typealias Stored = Int private let _stored: Int init(_ value: Int) { _stored = value } func getStored() -> Int { return _stored } } // An implementation that stores Strings struct StringStorer: StoringType { typealias Stored = String private let _stored: String init(_ value: String) { _stored = value } func getStored() -> String { return _stored } } let intStorer = IntStorer(5) intStorer.getStored() // returns 5 let stringStorer = StringStorer("five") stringStorer.getStored() // returns "five"
OK, na razie dobrze.
Teraz głównym powodem, dla którego typ zmiennej byłby protokół implementowany przez typ, a nie rzeczywisty typ, jest to, że możesz przypisać różne rodzaje obiektów, które wszystkie są zgodne z tym protokołem, do tej samej zmiennej i uzyskać polimorficzne zachowanie w czasie wykonywania w zależności od tego, czym właściwie jest obiekt.
Ale nie możesz tego zrobić, jeśli protokół ma powiązany typ. Jak w praktyce działałby poniższy kod?
// as you've seen this won't compile because // StoringType has an associated type. // randomly assign either a string or int storer to someStorer: var someStorer: StoringType = arc4random()%2 == 0 ? intStorer : stringStorer let x = someStorer.getStored()
Jaki byłby typ tego kodu w powyższym kodzie
x
? AnInt
? AlboString
? W Swift wszystkie typy muszą zostać naprawione w czasie kompilacji. Funkcja nie może dynamicznie przechodzić z zwracania jednego typu do innego na podstawie czynników określonych w czasie wykonywania.Zamiast tego można go używać tylko
StoredType
jako ogólnego ograniczenia. Załóżmy, że chcesz wydrukować dowolny rodzaj przechowywanego typu. Możesz napisać taką funkcję:func printStoredValue<S: StoringType>(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(stringStorer)
To jest w porządku, ponieważ w czasie kompilacji kompilator wypisze dwie wersje
printStoredValue
: jedną dlaInt
s, a drugą dlaString
s. W tych dwóch wersjachx
jest znany jako szczególny typ.źródło
p
zmiennej drukarki różnych typów, a następnie przekazać do niej nieprawidłowe typyprint
? Wyjątek w czasie wykonywania?var someStorer: StoringType<Int>
lubvar someStorer: StoringType<String>
i rozwiązać zarysowany problem.Jest jeszcze jedno rozwiązanie, które nie zostało wspomniane w tym pytaniu, a mianowicie użycie techniki zwanej wymazywaniem typów . Aby uzyskać abstrakcyjny interfejs dla protokołu ogólnego, utwórz klasę lub strukturę, która otacza obiekt lub strukturę zgodną z protokołem. Klasa opakowania, zwykle nazywana „Any {nazwa protokołu}”, sama jest zgodna z protokołem i implementuje swoje funkcje, przekazując wszystkie wywołania do obiektu wewnętrznego. Wypróbuj poniższy przykład na placu zabaw:
import Foundation public protocol Printer { typealias T func print(val:T) } struct AnyPrinter<U>: Printer { typealias T = U private let _print: U -> () init<Base: Printer where Base.T == U>(base : Base) { _print = base.print } func print(val: T) { _print(val) } } struct NSLogger<U>: Printer { typealias T = U func print(val: T) { NSLog("\(val)") } } let nsLogger = NSLogger<Int>() let printer = AnyPrinter(base: nsLogger) printer.print(5) // prints 5
Typ
printer
jest znanyAnyPrinter<Int>
i można go użyć do abstrakcji dowolnej możliwej implementacji protokołu drukarki. Chociaż AnyPrinter nie jest technicznie abstrakcyjna, jego implementacja jest tylko przejściem do prawdziwego typu implementującego i może być używana do oddzielania typów implementujących od typów, które ich używają.Należy zwrócić uwagę na to, że
AnyPrinter
nie musi jawnie zachowywać instancji podstawowej. W rzeczywistości nie możemy, ponieważ nie możemy zadeklarowaćAnyPrinter
posiadaniaPrinter<T>
właściwości. Zamiast tego otrzymujemy wskaźnik funkcji do funkcji_print
bazyprint
. Wywołaniebase.print
bez wywołania zwraca funkcję, w której baza jest pobierana jako zmienna self, a zatem jest zachowywana dla przyszłych wywołań.Inną rzeczą, o której należy pamiętać, jest to, że to rozwiązanie jest zasadniczo kolejną warstwą dynamicznej wysyłki, co oznacza niewielki spadek wydajności. Ponadto instancja wymazywania typu wymaga dodatkowej pamięci w górnej części podstawowej instancji. Z tych powodów wymazywanie typów nie jest bezpłatną abstrakcją.
Oczywiście jest trochę pracy, aby ustawić wymazywanie typów, ale może być bardzo przydatne, jeśli potrzebna jest abstrakcyjna abstrakcja protokołu. Ten wzorzec znajduje się w szybkiej bibliotece standardowej z typami takimi jak
AnySequence
. Dalsza lektura: http://robnapier.net/erasurePREMIA:
Jeśli zdecydujesz, że chcesz wstrzyknąć tę samą implementację
Printer
wszędzie, możesz zapewnić wygodny inicjator,AnyPrinter
który wstrzykuje ten typ.extension AnyPrinter { convenience init() { let nsLogger = NSLogger<T>() self.init(base: nsLogger) } } let printer = AnyPrinter<Int>() printer.print(10) //prints 10 with NSLog
Może to być łatwy i SUCHY sposób wyrażania iniekcji zależności dla protokołów używanych w całej aplikacji.
źródło
fatalError()
), która jest opisana w innych samouczkach dotyczących usuwania typów.Adresowanie zaktualizowanego przypadku użycia:
(przy okazji
Printable
to już standardowy protokół Swift, więc prawdopodobnie chciałbyś wybrać inną nazwę, aby uniknąć nieporozumień)Aby wymusić określone ograniczenia dotyczące implementatorów protokołu, można ograniczyć alias typu protokołu. Aby więc utworzyć kolekcję protokołów, która wymaga, aby elementy można było wydrukować:
// because of how how collections are structured in the Swift std lib, // you’d first need to create a PrintableGeneratorType, which would be // a constrained version of GeneratorType protocol PrintableGeneratorType: GeneratorType { // require elements to be printable: typealias Element: Printable } // then have the collection require a printable generator protocol PrintableCollectionType: CollectionType { typealias Generator: PrintableGenerator }
Jeśli chcesz zaimplementować kolekcję, która może zawierać tylko elementy do druku:
struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... }
Jednak jest to prawdopodobnie mało użyteczne, ponieważ nie możesz ograniczyć istniejących struktur kolekcji Swift, takich jak ta, tylko tych, które implementujesz.
Zamiast tego należy utworzyć funkcje ogólne, które ograniczają dane wejściowe do kolekcji zawierających elementy drukowalne.
func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } }
źródło