Chỉ để đảm bảo chúng ta ở trên cùng một trang, phương thức "ẩn" là khi một lớp dẫn xuất định nghĩa một thành viên cùng tên với một lớp trong lớp cơ sở (nếu đó là một phương thức / thuộc tính, không được đánh dấu ảo / overridable ) và khi được gọi từ một thể hiện của lớp dẫn xuất trong "bối cảnh dẫn xuất", thành viên dẫn xuất được sử dụng, trong khi nếu được gọi bởi cùng một thể hiện trong ngữ cảnh của lớp cơ sở của nó, thì thành viên lớp cơ sở được sử dụng. Điều này khác với trừu tượng / ghi đè thành viên trong đó thành viên lớp cơ sở mong muốn lớp dẫn xuất xác định thay thế và từ các bộ sửa đổi phạm vi / khả năng hiển thị "ẩn" một thành viên khỏi người tiêu dùng ngoài phạm vi mong muốn.
Câu trả lời ngắn gọn về lý do tại sao nó được cho phép là không làm như vậy sẽ buộc các nhà phát triển vi phạm một số nguyên lý chính của thiết kế hướng đối tượng.
Đây là câu trả lời dài hơn; trước tiên, hãy xem xét cấu trúc lớp sau trong một vũ trụ thay thế nơi C # không cho phép thành viên ẩn:
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";}}
}
Chúng tôi muốn bỏ ghi chú thành viên trong Bar và bằng cách đó, cho phép Bar cung cấp MyFooString khác. Tuy nhiên, chúng tôi không thể làm như vậy bởi vì nó sẽ vi phạm các quy định cấm thực tế thay thế đối với việc che giấu thành viên. Ví dụ cụ thể này sẽ đầy rẫy các lỗi và là một ví dụ điển hình về lý do tại sao bạn có thể muốn cấm nó; chẳng hạn, đầu ra giao diện điều khiển nào bạn sẽ nhận được nếu bạn làm như sau?
Bar myBar = new Bar();
Foo myFoo = myBar;
IFoo myIFoo = myFoo;
Console.WriteLine(myFoo.MyFooString);
Console.WriteLine(myBar.MyFooString);
Console.WriteLine(myIFoo.MyFooString);
Ngoài đỉnh đầu, tôi thực sự không chắc là bạn sẽ nhận được "Foo" hay "Bar" trên dòng cuối cùng đó. Bạn chắc chắn sẽ nhận được "Foo" cho dòng đầu tiên và "Bar" cho dòng thứ hai, mặc dù cả ba biến tham chiếu chính xác cùng một thể hiện với cùng một trạng thái.
Vì vậy, các nhà thiết kế của ngôn ngữ, trong vũ trụ thay thế của chúng ta, không khuyến khích mã rõ ràng xấu này bằng cách ngăn chặn tài sản che giấu. Bây giờ, bạn là một lập trình viên có nhu cầu thực sự để làm chính xác điều này. Làm thế nào để bạn có được xung quanh giới hạn? Chà, một cách là đặt tên tài sản của Bar khác nhau:
public class Bar:Foo
{
public string MyBarString {get{return "Bar";}}
}
Hoàn toàn hợp pháp, nhưng đó không phải là hành vi chúng ta muốn. Một phiên bản của Bar sẽ luôn tạo ra "Foo" cho thuộc tính MyFooString, khi chúng tôi muốn nó tạo ra "Bar". Chúng tôi không chỉ phải biết rằng IFoo của chúng tôi cụ thể là một Bar, chúng tôi còn phải biết sử dụng các bộ truy cập khác nhau.
Chúng ta cũng có thể, khá có lý, quên mối quan hệ cha-con và trực tiếp thực hiện giao diện:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
public int FooMethod() {...}
}
Đối với ví dụ đơn giản này, đó là một câu trả lời hoàn hảo, miễn là bạn chỉ quan tâm rằng Foo và Bar đều là IFoos. Mã sử dụng một vài ví dụ trở lên sẽ không thể biên dịch vì Bar không phải là Foo và không thể được chỉ định như vậy. Tuy nhiên, nếu Foo có một số phương thức hữu ích "FooMethod" mà Bar cần, thì bây giờ bạn không thể kế thừa phương thức đó; bạn sẽ phải sao chép mã của nó trong Bar hoặc sáng tạo:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
private readonly theFoo = new Foo();
public int FooMethod(){return theFoo.FooMethod();}
}
Đây là một vụ hack rõ ràng và trong khi một số triển khai thông số kỹ thuật ngôn ngữ OO ít hơn số này, về mặt khái niệm thì nó đã sai; nếu người tiêu dùng của Bar cần tiết lộ chức năng của Foo, thì Bar phải là Foo, không có Foo.
Rõ ràng, nếu chúng ta điều khiển Foo, chúng ta có thể biến nó thành ảo, sau đó ghi đè lên nó. Đây là cách thực hành tốt nhất về mặt khái niệm trong vũ trụ hiện tại của chúng ta khi một thành viên dự kiến sẽ bị ghi đè và sẽ nắm giữ trong bất kỳ vũ trụ thay thế nào không cho phép ẩn náu:
public class Foo:IFoo
{
public virtual string MyFooString {get{return "Foo";}}
//...
}
public class Bar:Foo
{
public override string MyFooString {get{return "Bar";}}
}
Vấn đề với điều này là việc truy cập thành viên ảo, dưới mui xe, tương đối tốn kém hơn để thực hiện, và vì vậy bạn thường chỉ muốn làm điều đó khi bạn cần. Tuy nhiên, việc không che giấu buộc bạn phải bi quan về các thành viên mà một lập trình viên khác không kiểm soát mã nguồn của bạn có thể muốn thực hiện lại; "cách thực hành tốt nhất" cho bất kỳ lớp không niêm phong nào sẽ là biến mọi thứ thành ảo trừ khi bạn đặc biệt không muốn nó trở thành. Nó cũng vẫn không cung cấp cho bạn hành vi che giấu chính xác; chuỗi sẽ luôn là "Bar" nếu thể hiện là Bar. Đôi khi nó thực sự hữu ích để tận dụng các lớp dữ liệu trạng thái ẩn, dựa trên mức độ kế thừa mà bạn đang làm việc.
Tóm lại, cho phép ẩn thành viên là ít tệ nạn hơn. Không có nó nói chung sẽ dẫn đến sự tàn bạo tồi tệ hơn cam kết chống lại các nguyên tắc hướng đối tượng hơn là cho phép nó.