Katakanlah saya memiliki protokol:
public protocol Printable {
typealias T
func Print(val:T)
}
Dan inilah implementasinya
class Printer<T> : Printable {
func Print(val: T) {
println(val)
}
}
Harapan saya adalah saya harus bisa menggunakan Printable
variabel untuk mencetak nilai seperti ini:
let p:Printable = Printer<Int>()
p.Print(67)
Kompiler mengeluhkan kesalahan ini:
"protokol 'Dapat dicetak' hanya dapat digunakan sebagai batasan umum karena memiliki persyaratan Jenis Sendiri atau terkait"
Apakah saya melakukan sesuatu yang salah? Pokoknya untuk memperbaikinya?
**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)
EDIT 2: Contoh dunia nyata dari apa yang saya inginkan. Perhatikan bahwa ini tidak akan dikompilasi, tetapi menyajikan apa yang ingin saya capai.
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()
}
Jawaban:
Seperti yang ditunjukkan Thomas, Anda dapat mendeklarasikan variabel Anda dengan tidak memberikan tipe sama sekali (atau Anda dapat secara eksplisit memberikannya sebagai tipe
Printer<Int>
. Tapi berikut adalah penjelasan mengapa Anda tidak dapat memiliki tipePrintable
protokol.Anda tidak dapat memperlakukan protokol dengan tipe terkait seperti protokol biasa dan mendeklarasikannya sebagai tipe variabel mandiri. Untuk memikirkan mengapa, pertimbangkan skenario ini. Misalkan Anda mendeklarasikan protokol untuk menyimpan beberapa jenis arbitrer dan kemudian mengambilnya kembali:
// 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"
Oke, sejauh ini bagus.
Sekarang, alasan utama Anda akan memiliki tipe variabel menjadi protokol yang diterapkan tipe, daripada tipe sebenarnya, adalah agar Anda dapat menetapkan berbagai jenis objek yang semuanya sesuai dengan protokol itu ke variabel yang sama, dan mendapatkan polimorfik perilaku saat runtime bergantung pada objek sebenarnya.
Tetapi Anda tidak dapat melakukan ini jika protokol memiliki tipe yang terkait. Bagaimana kode berikut bekerja dalam praktiknya?
// 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()
Pada kode di atas, akan seperti apakah tipe dari
x
itu? AnInt
? AtauString
? Di Swift, semua jenis harus diperbaiki pada waktu kompilasi. Sebuah fungsi tidak dapat bergeser secara dinamis dari mengembalikan satu jenis ke jenis lainnya berdasarkan faktor yang ditentukan pada waktu proses.Sebaliknya, Anda hanya dapat menggunakan
StoredType
sebagai batasan umum. Misalkan Anda ingin mencetak semua jenis jenis yang disimpan. Anda bisa menulis fungsi seperti ini:func printStoredValue<S: StoringType>(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(stringStorer)
Ini tidak masalah, karena pada waktu kompilasi, kompilator seperti menulis dua versi
printStoredValue
: satu untukInt
s, dan satu untukString
s. Di dalam dua versi itu,x
dikenal tipe tertentu.sumber
p
variabel Anda dan kemudian meneruskan jenis yang tidak valid keprint
? Pengecualian waktu proses?var someStorer: StoringType<Int>
atauvar someStorer: StoringType<String>
dan memecahkan masalah yang Anda garis besarkan.Ada satu solusi lagi yang belum disebutkan pada pertanyaan ini, yaitu menggunakan teknik yang disebut penghapusan tipe . Untuk mencapai antarmuka abstrak untuk protokol generik, buat kelas atau struct yang membungkus objek atau struct yang sesuai dengan protokol. Kelas pembungkus, biasanya bernama 'Any {protocol name}', itu sendiri sesuai dengan protokol dan mengimplementasikan fungsinya dengan meneruskan semua panggilan ke objek internal. Coba contoh di bawah ini di taman bermain:
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
Jenis dari
printer
telah diketahuiAnyPrinter<Int>
dan dapat digunakan untuk mengabstraksi segala kemungkinan implementasi dari protokol Printer. Meskipun AnyPrinter tidak abstrak secara teknis, implementasinya hanyalah jatuh ke tipe implementasi nyata, dan dapat digunakan untuk memisahkan tipe implementasi dari tipe yang menggunakannya.Satu hal yang perlu diperhatikan adalah
AnyPrinter
tidak harus secara eksplisit mempertahankan instance dasar. Faktanya, kami tidak bisa karena kami tidak bisa menyatakanAnyPrinter
memilikiPrinter<T>
properti. Sebagai gantinya, kita mendapatkan penunjuk fungsi_print
keprint
fungsi basis . Memanggilbase.print
tanpa memanggilnya mengembalikan fungsi di mana basis di-curried sebagai variabel mandiri, dan karenanya dipertahankan untuk pemanggilan di masa mendatang.Hal lain yang perlu diingat adalah bahwa solusi ini pada dasarnya adalah lapisan lain dari pengiriman dinamis yang berarti sedikit berpengaruh pada kinerja. Selain itu, instance penghapus tipe memerlukan memori tambahan di atas instance yang mendasarinya. Untuk alasan ini, penghapusan tipe bukanlah abstraksi bebas biaya.
Jelas ada beberapa pekerjaan untuk mengatur penghapusan tipe, tetapi ini bisa sangat berguna jika abstraksi protokol umum diperlukan. Pola ini ditemukan di pustaka standar swift dengan tipe seperti
AnySequence
. Bacaan lebih lanjut: http://robnapier.net/erasureBONUS:
Jika Anda memutuskan ingin memasukkan implementasi yang sama di semua
Printer
tempat, Anda dapat menyediakan penginisialisasi praktisAnyPrinter
yang memasukkan jenis tersebut.extension AnyPrinter { convenience init() { let nsLogger = NSLogger<T>() self.init(base: nsLogger) } } let printer = AnyPrinter<Int>() printer.print(10) //prints 10 with NSLog
Ini bisa menjadi cara yang mudah dan KERING untuk mengekspresikan injeksi dependensi untuk protokol yang Anda gunakan di seluruh aplikasi Anda.
sumber
fatalError()
) yang dijelaskan dalam tutorial penghapusan tipe lainnya.Menangani kasus penggunaan terbaru Anda:
(btw
Printable
sudah menjadi protokol Swift standar jadi Anda mungkin ingin memilih nama yang berbeda untuk menghindari kebingungan)Untuk menerapkan batasan khusus pada pelaksana protokol, Anda dapat membatasi typealias protokol. Jadi untuk membuat koleksi protokol Anda yang membutuhkan elemen agar dapat dicetak:
// 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 }
Sekarang jika Anda ingin menerapkan koleksi yang hanya dapat berisi elemen yang dapat dicetak:
struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... }
Namun, ini mungkin utilitas kecil yang sebenarnya, karena Anda tidak dapat membatasi struct koleksi Swift yang ada seperti itu, hanya yang Anda terapkan.
Sebagai gantinya, Anda harus membuat fungsi umum yang membatasi inputnya ke koleksi yang berisi elemen yang dapat dicetak.
func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } }
sumber