Hanya untuk memastikan kita berada di halaman yang sama, metode "persembunyian" adalah ketika kelas turunan mendefinisikan anggota dengan nama yang sama dengan yang ada di kelas dasar (yang, jika itu adalah metode / properti, tidak ditandai virtual / dapat diganti) ), dan ketika dipanggil dari turunan kelas turunan dalam "konteks turunan", anggota turunan digunakan, sedangkan jika dipanggil oleh turunan yang sama dalam konteks kelas dasarnya, anggota kelas dasar digunakan. Ini berbeda dengan abstraksi anggota / pengesampingan di mana anggota kelas dasar mengharapkan kelas turunan untuk menentukan pengganti, dan dari pengubah lingkup / visibilitas yang "menyembunyikan" anggota dari konsumen di luar ruang lingkup yang diinginkan.
Jawaban singkat mengapa diizinkan adalah bahwa tidak melakukannya akan memaksa pengembang untuk melanggar beberapa prinsip utama desain berorientasi objek.
Inilah jawaban yang lebih panjang; pertama, pertimbangkan struktur kelas berikut di alam semesta alternatif tempat C # tidak mengizinkan anggota bersembunyi:
public interface IFoo
{
string MyFooString {get;}
int FooMethod();
}
public class Foo:IFoo
{
public string MyFooString {get{return "Foo";}}
public int FooMethod() {//incredibly useful code here};
}
public class Bar:Foo
{
//public new string MyFooString {get{return "Bar";}}
}
Kami ingin menghapus komentar anggota di Bar, dan dengan melakukan itu, izinkan Bar untuk memberikan MyFooString yang berbeda. Namun, kami tidak dapat melakukannya karena akan melanggar larangan realitas alternatif pada persembunyian anggota. Contoh khusus ini akan penuh dengan bug dan merupakan contoh utama mengapa Anda mungkin ingin melarangnya; misalnya, output konsol apa yang akan Anda dapatkan jika Anda melakukan yang berikut?
Bar myBar = new Bar();
Foo myFoo = myBar;
IFoo myIFoo = myFoo;
Console.WriteLine(myFoo.MyFooString);
Console.WriteLine(myBar.MyFooString);
Console.WriteLine(myIFoo.MyFooString);
Dari atas kepala saya, saya sebenarnya tidak yakin apakah Anda akan mendapatkan "Foo" atau "Bar" di baris terakhir itu. Anda pasti akan mendapatkan "Foo" untuk baris pertama dan "Bar" untuk yang kedua, meskipun ketiga variabel referensi contoh yang sama persis dengan keadaan yang sama persis.
Jadi, perancang bahasa, di alam semesta alternatif kita, mencegah kode yang jelas-jelas buruk ini dengan mencegah persembunyian properti. Sekarang, Anda sebagai pembuat kode harus benar-benar melakukan ini. Bagaimana Anda mengatasi batasan itu? Salah satu caranya adalah memberi nama properti Bar secara berbeda:
public class Bar:Foo
{
public string MyBarString {get{return "Bar";}}
}
Sangat legal, tetapi itu bukan perilaku yang kita inginkan. Sebuah instance dari Bar akan selalu menghasilkan "Foo" untuk properti MyFooString, ketika kami menginginkannya untuk menghasilkan "Bar". Kita tidak hanya harus tahu bahwa IFoo kita secara khusus adalah Bar, kita juga harus tahu untuk menggunakan pengakses yang berbeda.
Kami juga bisa, cukup masuk akal, melupakan hubungan orangtua-anak dan mengimplementasikan antarmuka secara langsung:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
public int FooMethod() {...}
}
Untuk contoh sederhana ini, ini adalah jawaban yang sempurna, selama Anda hanya peduli bahwa Foo dan Bar sama-sama IFOO. Kode penggunaan beberapa contoh akan gagal dikompilasi karena Bar bukan Foo dan tidak dapat ditetapkan seperti itu. Namun, jika Foo memiliki beberapa metode yang berguna "FooMethod" yang dibutuhkan Bar, sekarang Anda tidak dapat mewarisi metode itu; Anda harus mengkloning kode di Bar, atau menjadi kreatif:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
private readonly theFoo = new Foo();
public int FooMethod(){return theFoo.FooMethod();}
}
Ini adalah peretasan yang jelas, dan sementara beberapa implementasi spesifikasi bahasa OO berjumlah sedikit lebih banyak dari ini, secara konseptual itu salah; jika konsumen dari Bar kebutuhan untuk mengekspos fungsi Foo, Bar harus menjadi Foo, tidak memiliki Foo.
Jelas, jika kita mengendalikan Foo, kita bisa membuatnya virtual, lalu menimpanya. Ini adalah praktik konseptual terbaik di alam semesta kita saat ini ketika seorang anggota diperkirakan akan ditimpa, dan akan bertahan di alam semesta alternatif apa pun yang tidak memungkinkan bersembunyi:
public class Foo:IFoo
{
public virtual string MyFooString {get{return "Foo";}}
//...
}
public class Bar:Foo
{
public override string MyFooString {get{return "Bar";}}
}
Masalah dengan ini adalah bahwa akses anggota virtual, di bawah tenda, relatif lebih mahal untuk dilakukan, dan Anda biasanya hanya ingin melakukannya ketika Anda perlu. Kurangnya persembunyian, bagaimanapun, memaksa Anda untuk menjadi pesimis tentang anggota yang pembuat kode lain yang tidak mengontrol kode sumber Anda mungkin ingin menerapkan kembali; "praktik terbaik" untuk kelas yang tidak tersegel adalah membuat semuanya virtual kecuali Anda tidak menginginkannya secara khusus. Itu juga masih tidak memberi Anda perilaku persembunyian yang tepat; string akan selalu menjadi "Bar" jika instance adalah Bar. Kadang-kadang benar-benar bermanfaat untuk memanfaatkan lapisan data keadaan tersembunyi, berdasarkan tingkat warisan tempat Anda bekerja.
Singkatnya, membiarkan persembunyian anggota adalah kejahatan yang lebih kecil. Tidak memilikinya pada umumnya akan mengarah pada kekejaman yang lebih buruk yang dilakukan terhadap prinsip-prinsip berorientasi objek daripada membiarkannya.