Saat Google, banyak tanggapan untuk topik ini muncul. Namun, saya tidak merasa ada di antara mereka melakukan pekerjaan yang baik untuk menggambarkan perbedaan antara kedua fitur ini. Jadi saya ingin mencoba sekali lagi, khususnya ...
Apa yang bisa dilakukan dengan tipe diri dan bukan dengan warisan, dan sebaliknya?
Bagi saya, harus ada beberapa perbedaan fisik yang dapat dikuantifikasi di antara keduanya, jika tidak keduanya hanya berbeda secara nominal.
Jika Sifat A meluas B atau tipe diri B, tidakkah keduanya menggambarkan bahwa menjadi B adalah persyaratan? Dimana perbedaannya?
design-patterns
object-oriented-design
scala
Mark Canlas
sumber
sumber
Jawaban:
Jika sifat A memanjang B, maka mencampurkan dalam A memberi Anda tepat B ditambah apa pun yang ditambahkan atau diperluas oleh A. Sebaliknya, jika sifat A memiliki referensi diri yang secara eksplisit diketik sebagai B, maka kelas induk akhir juga harus dicampur dalam B atau tipe B turunan (dan mencampurnya terlebih dahulu , yang penting).
Itulah perbedaan terpenting. Dalam kasus pertama, tipe B yang tepat dikristalisasi pada titik A yang meluas. Pada yang kedua, perancang kelas induk dapat memutuskan versi B yang digunakan, pada titik di mana kelas induk disusun.
Perbedaan lainnya adalah di mana A dan B menyediakan metode dengan nama yang sama. Di mana A meluas B, metode A menimpa B. Di mana A dicampur setelah B, metode A hanya menang.
Referensi diri yang diketik memberi Anda lebih banyak kebebasan; kopling antara A dan B longgar.
MEMPERBARUI:
Karena Anda tidak jelas tentang manfaat perbedaan ini ...
Jika Anda menggunakan pewarisan langsung, maka Anda membuat sifat A yang merupakan B + A. Anda telah mengatur hubungan di atas batu.
Jika Anda menggunakan referensi diri yang diketik, maka siapa pun yang ingin menggunakan sifat A Anda di kelas C bisa
Dan ini bukan batas dari pilihan mereka, mengingat cara Scala memungkinkan Anda untuk instantiate sifat secara langsung dengan blok kode sebagai konstruktornya.
Adapun perbedaan antara metode menang A, karena A dicampur pada akhirnya, dibandingkan dengan A memperluas B, pertimbangkan ini ...
Di mana Anda mencampur dalam urutan sifat, setiap kali metode
foo()
dipanggil, kompilator pergi ke sifat terakhir yang dicampur untuk mencarifoo()
, lalu (jika tidak ditemukan), itu melintasi urutan ke kiri sampai menemukan sifat yang mengimplementasikanfoo()
dan menggunakan bahwa. A juga memiliki opsi untuk memanggilsuper.foo()
, yang juga melintasi urutan ke kiri hingga menemukan implementasi, dan seterusnya.Jadi jika A memiliki referensi diri yang diketik untuk B dan penulis A tahu bahwa B mengimplementasikan
foo()
, A dapat memanggilsuper.foo()
mengetahui bahwa jika tidak ada yang lain menyediakanfoo()
, B akan melakukannya. Namun, pencipta kelas C memiliki opsi untuk menghapus semua sifat lain yang diimplementasikanfoo()
, dan A akan mendapatkannya.Sekali lagi, ini jauh lebih kuat dan tidak terlalu membatasi daripada A memperluas B dan langsung memanggil versi B dari
foo()
.sumber
Saya memiliki beberapa kode yang menggambarkan beberapa perbedaan visibilitas visibilitas dan implementasi "default" ketika memperluas vs pengaturan tipe diri. Itu tidak menggambarkan salah satu bagian yang sudah dibahas tentang bagaimana tabrakan nama aktual diselesaikan, tetapi berfokus pada apa yang mungkin dan tidak mungkin dilakukan.
Satu perbedaan penting IMO adalah bahwa
A1
tidak mengekspos bahwa itu perluB
untuk apa pun yang hanya melihatnya sebagaiA1
(seperti diilustrasikan di bagian atas pemain). Satu-satunya kode yang benar-benar akan melihat bahwa spesialisasi tertentuB
digunakan, adalah kode yang secara eksplisit tahu tentang tipe yang dikomposisikan (sepertiThink*{X,Y}
).Poin lain adalah bahwa
A2
(dengan ekstensi) akan benar-benar digunakanB
jika tidak ada yang lain yang ditentukan sementaraA1
(tipe diri) tidak mengatakan bahwa itu akan digunakanB
kecuali diganti, beton B harus diberikan secara eksplisit ketika objek dipakai.sumber