Kembali pada tahun 2000-an, seorang kolega saya mengatakan kepada saya bahwa itu adalah anti-pola untuk membuat metode publik virtual atau abstrak.
Misalnya, ia menganggap kelas seperti ini tidak dirancang dengan baik:
public abstract class PublicAbstractOrVirtual
{
public abstract void Method1(string argument);
public virtual void Method2(string argument)
{
if (argument == null) throw new ArgumentNullException(nameof(argument));
// default implementation
}
}
Dia menyatakan itu
- pengembang kelas turunan yang mengimplementasikan
Method1
dan menimpaMethod2
harus mengulangi validasi argumen. - dalam hal pengembang kelas dasar memutuskan untuk menambahkan sesuatu di sekitar bagian yang dapat dikustomisasi
Method1
atau yangMethod2
lebih baru, ia tidak dapat melakukannya.
Sebagai gantinya, kolega saya mengusulkan pendekatan ini:
public abstract class ProtectedAbstractOrVirtual
{
public void Method1(string argument)
{
if (argument == null) throw new ArgumentNullException(nameof(argument));
this.Method1Core(argument);
}
public void Method2(string argument)
{
if (argument == null) throw new ArgumentNullException(nameof(argument));
this.Method2Core(argument);
}
protected abstract void Method1Core(string argument);
protected virtual void Method2Core(string argument)
{
// default implementation
}
}
Dia mengatakan kepada saya membuat metode publik (atau properti) virtual atau abstrak sama buruknya dengan membuat bidang publik. Dengan membungkus bidang ke dalam properti, seseorang dapat mencegat akses apa pun ke bidang itu nanti, jika perlu. Hal yang sama berlaku untuk anggota virtual / abstrak publik: membungkus mereka seperti yang ditunjukkan dalam ProtectedAbstractOrVirtual
kelas memungkinkan pengembang kelas dasar untuk mencegat panggilan yang masuk ke metode virtual / abstrak.
Tapi saya tidak melihat ini sebagai pedoman desain. Bahkan Microsoft tidak mengikutinya: lihat saja Stream
kelas untuk memverifikasi ini.
Apa pendapat Anda tentang pedoman itu? Apakah itu masuk akal, atau Anda pikir itu terlalu rumit API?
sumber
virtual
memungkinkan penggantian opsional. Metode Anda mungkin harus bersifat publik, karena mungkin tidak diganti. Membuat metodeabstract
memaksa Anda untuk menimpanya; mereka seharusnyaprotected
, karena mereka tidak terlalu berguna dalam suatupublic
konteks.protected
ini paling berguna ketika Anda ingin mengekspos anggota pribadi dari kelas abstrak ke kelas turunan. Bagaimanapun, saya tidak terlalu peduli dengan pendapat teman Anda; pilih pengubah akses yang paling masuk akal untuk situasi khusus Anda.Jawaban:
Pepatah
mencampuradukkan sebab dan akibat. Itu membuat asumsi bahwa setiap metode yang dapat ditimpa memerlukan validasi argumen yang tidak dapat disesuaikan. Namun sebaliknya:
Jika seseorang ingin merancang metode dengan cara yang menyediakan beberapa validasi argumen tetap di semua derivasi kelas (atau - lebih umum - bagian yang dapat disesuaikan dan tidak dapat disesuaikan), maka masuk akal untuk membuat titik masuknya non-virtual , dan sebagai gantinya menyediakan metode virtual atau abstrak untuk bagian yang dapat disesuaikan yang disebut secara internal.
Tapi ada banyak contoh di mana itu masuk akal untuk memiliki metode virtual publik, karena tidak ada tetap non-disesuaikan bagian: lihat metode standar seperti
ToString
atauEquals
atauGetHashCode
- akan itu meningkatkan desainobject
kelas untuk memiliki ini tidak umum dan virtual pada saat bersamaan? Saya kira tidak.Atau, dalam hal kode Anda sendiri: ketika kode di kelas dasar akhirnya dan sengaja terlihat seperti ini
pemisahan antara
Method1
danMethod1Core
hanya mempersulit hal-hal tanpa alasan yang jelas.sumber
ToString()
metode, Microsoft lebih baik membuatnya non-virtual dan memperkenalkan metode templat virtualToStringCore()
. Mengapa: karena ini:ToString()
- catatan untuk pewaris . Mereka menyatakan bahwaToString()
seharusnya tidak mengembalikan null. Mereka bisa saja memaksa permintaan ini dengan menerapkanToString() => ToStringCore() ?? string.Empty
.string.Empty
, apakah Anda perhatikan? Dan itu merekomendasikan banyak hal lain yang tidak dapat ditegakkan dalam kode dengan memperkenalkan sesuatu sepertiToStringCore
metode. Jadi teknik ini mungkin bukan alat yang tepat untukToString
.anyObjectIGot.ToString()
bukannyaanyObjectIGot?.ToString()
" - bagaimana ini relevan?ToStringCore()
Pendekatan Anda akan mencegah string null dikembalikan, tetapi masih akan melemparNullReferenceException
jika objek tersebut null.public virtual
, tetapi ada beberapa kasus yangpublic virtual
ok. Kita bisa berargumen untuk bagian yang tidak dapat dikustomisasi kosong sebagai pembuatan kode masa depan bukti ... Tapi itu tidak berhasil. Kembali dan mengubahnya dapat merusak tipe turunan. Jadi, itu tidak menghasilkan apa-apa.Melakukannya seperti yang disarankan oleh kolega Anda memang memberikan lebih banyak fleksibilitas kepada implementor kelas dasar. Tetapi dengan itu juga muncul lebih banyak kompleksitas yang biasanya tidak dibenarkan oleh manfaat yang diperkirakan.
Pikiran bahwa peningkatan fleksibilitas untuk pelaksana kelas dasar datang dengan mengorbankan fleksibilitas yang lebih rendah kepada pihak utama. Mereka mendapatkan perilaku yang dipaksakan yang mungkin tidak mereka pedulikan. Bagi mereka hal-hal baru saja menjadi lebih kaku. Ini dapat dibenarkan dan bermanfaat tetapi ini semua tergantung pada skenario.
Konvensi penamaan untuk mengimplementasikan ini (yang saya tahu) adalah untuk mencadangkan nama baik untuk antarmuka publik dan untuk mengawali nama metode internal dengan "Do".
Satu kasus yang bermanfaat adalah ketika tindakan yang dilakukan membutuhkan beberapa pengaturan dan beberapa penutupan. Seperti membuka aliran dan menutupnya setelah overrider selesai. Secara umum, inisialisasi dan finalisasi yang sama. Ini adalah pola yang valid untuk digunakan tetapi tidak ada gunanya untuk mengamanatkan penggunaannya dalam semua skenario abstrak dan virtual.
sumber
BindingList
. Selain itu saya menemukan rekomendasi di suatu tempat, mungkin Pedoman Desain Kerangka mereka atau yang serupa. Dan: ini adalah postfix . ;-)Dalam C ++, ini disebut pola antarmuka non-virtual (NVI). (Sekali waktu, itu disebut Metode Templat. Itu membingungkan, tetapi beberapa artikel yang lebih tua memiliki terminologi itu.) NVI dipromosikan oleh Herb Sutter, yang telah menulis tentang hal itu setidaknya beberapa kali. Saya pikir salah satu yang paling awal ada di sini .
Jika saya ingat dengan benar, premisnya adalah bahwa kelas turunan tidak boleh mengubah apa yang dilakukan kelas dasar tetapi bagaimana melakukannya.
Misalnya, Bentuk mungkin memiliki metode Pindahkan untuk memindahkan bentuk. Implementasi konkret (mis., Seperti Kotak dan Lingkaran) tidak boleh secara langsung mengesampingkan Move, karena Shape mendefinisikan apa arti Moving (pada level konseptual). Kotak mungkin memiliki detail implementasi yang berbeda dari Lingkaran dalam hal bagaimana posisi diwakili secara internal, sehingga mereka harus mengganti beberapa metode untuk menyediakan fungsionalitas Pindah.
Dalam contoh sederhana, ini sering bermuara pada Pindah publik yang hanya mendelegasikan semua pekerjaan ke virtual pribadi ReallyDoTheMove, jadi sepertinya banyak overhead tanpa manfaat.
Tetapi korespondensi satu-satu ini bukan keharusan. Misalnya, Anda bisa menambahkan metode Animate ke API publik Shape, dan mungkin menerapkannya dengan memanggil ReallyDoTheMove dalam satu lingkaran. Anda berakhir dengan dua metode publik non-virtual API yang keduanya bergantung pada satu metode abstrak pribadi. Lingkaran dan Kuadrat Anda tidak perlu melakukan pekerjaan ekstra apa pun, dan Override Animate juga tidak bisa .
Kelas dasar mendefinisikan antarmuka publik yang digunakan oleh konsumen, dan mendefinisikan antarmuka operasi primitif yang diperlukan untuk mengimplementasikan metode publik tersebut. Tipe turunannya bertanggung jawab untuk menyediakan implementasi operasi primitif tersebut.
Saya tidak mengetahui adanya perbedaan antara C # dan C ++ yang akan mengubah aspek desain kelas ini.
sumber