2009-07-14 11 views
17

Bản chất của vấn đề là, đưa ra một hệ thống phân cấp lớp như thế này:Làm thế nào để ẩn (loại bỏ) một phương thức của lớp cơ sở trong C#?

class A 
{ 
    protected void MethodToExpose() 
    {} 

    protected void MethodToHide(object param) 
    {} 
} 

class B : A 
{ 
    new private void MethodToHide(object param) 
    {} 

    protected void NewMethodInB() 
    {} 
} 

class C : B 
{ 
    public void DoSomething() 
    { 
     base.MethodToHide("the parameter"); // This still calls A.MethodToHide() 
     base.MethodToExpose(); // This calls A.MethodToExpose(), but that's ok 
     base.NewMethodInB(); 
    } 
} 

Làm thế nào tôi có thể ngăn chặn bất kỳ lớp kế thừa từ lớp "B" từ nhìn thấy những phương pháp A.MethodToHide()? Trong C++, điều này đã đủ dễ dàng bằng cách sử dụng một khai báo như class B : private A, nhưng cú pháp này không hợp lệ trong C#.

Đối với những người quan tâm (hoặc tự hỏi tôi là gì thực sự cố gắng làm), những gì chúng tôi đang cố gắng làm là tạo trình bao bọc cho Rhino.Commons.NHRepository ẩn các phương pháp mà chúng tôi không muốn tiếp xúc với nhóm các nhà phát triển của chúng tôi, vì vậy chúng tôi có thể có một cách cắt cookie để phát triển ứng dụng của chúng tôi mà các nhà phát triển mới có thể dễ dàng theo dõi. Vì vậy, có, tôi tin rằng thử nghiệm "Is-A" là hợp lệ cho toàn bộ chuỗi (WidgetRepository Is-A BaseRepository Is-A NHRepository).

Chỉnh sửa: Tôi nên đề cập, vì lợi ích của đối số, lớp A là lớp API ngoài tầm kiểm soát của chúng tôi. Nếu không, vấn đề sẽ dễ dàng hơn nhiều.

Trả lời

21

Bạn không thể thực hiện và duy trì cấu trúc phân cấp. Nếu có thể, bạn nên tạo các giao diện xác định lý tưởng của bạn, sau đó phân lớp các lớp cơ sở và triển khai thực hiện các giao diện. chỉ tham chiếu các giao diện (không phải các kiểu lớp cơ sở) trong mã của bạn.

Adapter pattern được tạo riêng để giải quyết vấn đề về cách sử dụng khung khi API của nó không khớp chính xác với nhu cầu của bạn.

+0

+1: Tôi thích giải pháp này! Miễn là các nhà phát triển gắn bó với giao diện và không đưa nó một cách rõ ràng vào lớp tùy chỉnh, họ sẽ không có quyền truy cập vào bất kỳ thứ gì ngoài những gì được chỉ định trong giao diện. – Blixt

+1

Đó là một ý tưởng khéo léo. Điều này là dọc theo dòng của những gì chúng tôi đã làm (sử dụng NHibernate, MVC, Dependency Injection, ...), vì vậy chúng tôi đã có giao diện cho các kho (lớp C). Điều đó giữ cho khách hàng của lớp C trong bóng tối về những gì dưới-the-cover. Tất cả những gì còn lại phải lo lắng là cách C được triển khai. Trong trường hợp của chúng ta, sẽ có nhiều lớp con "C" (kho lưu trữ cho các đối tượng khác nhau). Vì vậy, lý tưởng của chúng tôi có lẽ có B là một wrapper/adapter để A. Thật không may, A có rất nhiều phương pháp để quyết định nếu chúng ta muốn ẩn hoặc lộ trong B. –

14

Nếu bạn muốn tùy chỉnh chỉnh một tập hợp các đối tượng địa lý, tôi muốn nói bạn muốn tạo trình bao bọc thay vì kế thừa chức năng. Tôi không biết cách nào để làm những gì bạn muốn trong C#. Hãy suy nghĩ về điều đó, nếu một số mã nằm ngoài tầm kiểm soát của bạn muốn ví dụ này là NHRepository cho thứ gì đó ... nhưng bạn đã xóa chức năng này khỏi chức năng cần thiết trong lớp con của bạn (mà bạn sẽ gửi, vì đó là của bạn chỉ có NHRepository trường hợp.) Mọi thứ đều bùng nổ. Đó là lý do tại sao tôi không nghĩ rằng nó thậm chí có thể làm mà không có một số hack xấu xí.

+3

1 Tôi muốn đặt cược vào làm một wrapper là tốt, thay vì thừa kế. Thay vì tập trung vào những gì bạn muốn ẩn, bạn có thể tập trung vào những gì bạn muốn phơi bày. –

+0

Tôi nghĩ rằng sự phù hợp gần nhất sẽ là ghi đè lên hoặc khai báo các phương thức mới trong lớp con ném NotSupportedExceptions - chắc chắn xấu xí! – STW

+0

@Yoooder: Tôi nghi ngờ tất cả các phương thức đều là 'virtual' nên chúng không thể bị ghi đè (và việc ghi đè có thể làm rối loạn hành vi của cả lớp) và cố gắng ẩn chúng bằng' mới' dường như là thứ mà anh ta đã thử (xem ví dụ của mình.) – Blixt

0

Mã của bạn trong lớp dẫn xuất B sẽ không ẩn phương thức cơ sở từ tất cả các loại có nguồn gốc, chỉ chính nó. Bạn sẽ phải đặt phương thức thành riêng tư trong cơ sở. Cách duy nhất xung quanh vấn đề này là tạo một lớp cơ sở khác không đưa ra phương thức này.

1

C# không có khái niệm tương tự như kế thừa bảo vệ hoặc riêng tư trong C++.

Tùy chọn tốt nhất của bạn là tổng hợp một thể hiện của lớp và hiển thị bộ phương pháp mà bạn quan tâm đến người tiêu dùng có quyền truy cập. Mặc dù trong trường hợp của bạn, tôi không nghĩ rằng nó có thể, bạn có thể xem xét việc tạo ra một giao diện cho thấy chức năng phổ biến mà bạn muốn người tiêu dùng làm việc với nhau để trình bao bọc của bạn có thể thay thế được trong một số trường hợp.

1

Tôi không bao giờ biết bạn có thể làm điều đó trong C++ mặc dù tôi không biết nhiều về C++. Tôi sợ rằng tôi đồng ý với Blixt rằng lớp học bao bọc có lẽ là cách tôi thực hiện điều này.

Có thể hoạt động (nhưng tôi không chắc) chỉ cần ghi đè hàm và ném ngoại lệ khi có cuộc gọi ....

+0

Điều này không hoạt động. Nhà phát triển đồng nghiệp của tôi đang hướng tới giải pháp này, nhưng tôi không hài lòng vì nó không có vẻ sạch sẽ. –

+1

C++ là ngôn ngữ dứt khoát "bạn có thể làm bất cứ điều gì bạn muốn", với rất nhiều tính linh hoạt đáng ngạc nhiên. Bao gồm cả sự linh hoạt để tự bắn mình vào chân, treo mình, tự mình chạy trên xe buýt ... –

6

Nếu bạn đang sử dụng Visual Studio, bạn có thể ẩn các phương pháp/tài sản từ intelliprompt với thuộc tính này:

class A 
{ 
    protected void MethodToExpose() 
    {} 

    [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)] 
    protected void MethodToHide(object param) 
    {} 
} 

Nó sẽ không thực sự thoát khỏi chức năng, nhưng nếu họ chỉ là những người nội bộ của bạn . Nó có thể đủ gần.

+1

Tôi quên đề cập đến khi lần đầu tiên tôi đăng rằng "lớp A" là một lớp API ngoài tầm kiểm soát của chúng tôi. Vì vậy, sửa đổi lớp A là ra ngoài. Tuy nhiên, tôi sẽ chơi xung quanh với điều này. [System.ComponentModel.EditorBrowsable (...)] có vẻ là một tính năng tiện dụng có thể hữu ích tại một số điểm! –

+1

Bạn có thể thêm vào thuộc tính trong lớp B. –

+1

Tôi không chắc đây có phải là một ý tưởng thậm chí nên được xem xét trong một giây hay không. Nó chắc chắn sẽ gây ra sự nhầm lẫn, mà sẽ thêm vào nợ kỹ thuật. – SideFX

1

Theo như tôi biết, bạn không thể ẩn nó theo cách bạn muốn. Tôi nghĩ bạn có thể ngăn chặn nó được sử dụng trong bất cứ cách thực tế mặc dù:

class B : A 
{ 
    public new void MethodToHide(object param) 
    { 
     throw new DontUseThisMethodException(); 
    } 

    protected void NewMethodInB() 
    {} 
} 

Không phải là điều đẹp nhất để làm mặc dù, vì vậy bạn có lẽ sẽ muốn giải quyết nó trong một số cách khác ...

+2

Đồng nghiệp của tôi đã quyết định sử dụng phương pháp này. Nó đẹp hơn để ẩn nó tại thời gian biên dịch thay vì thời gian chạy, mặc dù. –

+1

nó là NO NO để ném notimplementedException mới hoặc một số loại .. nó vi phạm Nguyên tắc Phân đoạn Giao diện – user384080

1

Trên thực tế bạn CÓ THỂ ẩn phương thức của A từ C nếu bạn đã xác định phương thức của B khi có thể truy cập từ C.

Vấn đề duy nhất với mã của bạn là bạn sử dụng "riêng tư" trên khai báo ẩn của mình ... nếu bạn sử dụng bảo vệ hoặc công khai bạn sẽ không có bất kỳ vấn đề nào và nó sẽ hoạt động như bạn mong đợi. Tôi làm điều này tất cả các thời gian với các lĩnh vực.

+0

Tôi nghĩ rằng tôi thấy những gì bạn đang cố gắng để nói, nhưng tôi thực sự không muốn lớp C để xem bất kỳ phiên bản của MethodToHide (param), cho dù đó là thừa hưởng từ B hoặc A. Một giải pháp của đồng nghiệp là dọc theo dòng của những gì bạn đề nghị, mặc dù. Ông ghi đè MethodToHide (param) trong B, nhưng có thêm bước làm B.MethodToHide (param) ném một NotImplementedException. Tương tự như giải pháp của Fredrick. –

+0

@ OgrePsalm33: Tạo một số thành viên khác với tên đó và chữ ký không tương thích - giống như một thuộc tính chỉ ghi có kiểu 'enum' được gọi là' DontEvenThinkAboutIt', thành viên duy nhất được xác định là 'ItWontWork'. – supercat

18

Obsolete Nó

Trong lớp B, ghi đè MethodToHide và thêm thuộc tính Obsolete

[Obsolete("Reason", true)] // true will cause a compile-time error 

Set EditorBrowsable

(Như đã đề cập trước đó)

Trong lớp B , ghi đè MethodToHide và thêm EditorBrowsa ble thuộc tính

[System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)] 

Ném ngoại lệ

(Như đã đề cập trước đó)

Trong lớp B, ghi đè MethodToHide và ném ngoại lệ.

Tạo Wrapper

Tôi nghĩ Michael Meadows là đúng. Sử dụng Adapter pattern. Mẫu này cũng cho phép dễ dàng chế nhạo mã khi kiểm tra đơn vị.

class B: IInterface 
{  
    protected void MethodToExpose() 
    { 
     A a = new A(); 
     a.MethodToExpose(); 
    } 

    protected void NewMethodInB() 
    { 
    } 
} 
+1

Cách tiếp cận thú vị! Tôi ngạc nhiên là chưa có ai đề cập đến thuộc tính Lỗi thời. Có lẽ không hoàn toàn là ý định ban đầu của thuộc tính, nhưng không bao giờ ít hơn, tôi nghĩ thích hợp. –

+0

Làm cách nào để thêm thành viên mới của một loại hoàn toàn khác? Có lẽ một lớp tĩnh công cộng rỗng? Tôi không chắc chắn chính xác tất cả các ngôn ngữ .NET xử lý shadowing như thế nào, nhưng tôi nghĩ rằng lớp mới sẽ ẩn tất cả các thành viên lớp cơ sở có cùng tên. – supercat

+0

Việc thêm thuộc tính 'Lỗi thời 'nghe có vẻ giống như một ý tưởng hay, nhưng tiếc là nó sẽ ném [cảnh báo trình biên dịch] (http://msdn.microsoft.com/en-us/library/bb397626 (v = vs.90) .aspx). Thuộc tính lỗi cũng sẽ không có hiệu lực. –

4

Theo tôi, cách tốt nhất để làm điều đó, nếu bạn chỉ muốn ẩn và không thực sự ghi đè và bên ngoài chức năng mới, chỉ cần thêm thuộc tính EditorBrowsableState. Nó ẩn phương thức trong trình chỉnh sửa studio trực quan. Ai sẽ cố gắng sử dụng nó sẽ kết thúc với một lỗi thời gian biên dịch.

Chỉ cần thêm mã này trên phương pháp của bạn:

[System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)] 

Ví dụ

public class User 
{   
    public void DoSomething() 
    { 
     Something... 
    } 
} 

public class Manager 
{ 
    [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)] 
    public override void DoSomething() 
    { } 
} 

void main() 
{ 
    User user = new User(); 
    user.DoSomething(); 

    Manager manager = new Manager(); 
    manager.DoSomething(); // --------- This row will throw a design time error 
} 

Good Luck :)

+0

'[System.ComponentModel.EditorBrowsable (EditorBrowsableState.Never)] 'không ** không ** ném một lỗi thời gian thiết kế trong LINQPad. Thậm chí nhiều hơn như vậy: nó ** được ** hiển thị bởi Intellisense. Chưa được kiểm tra với Visual Studio, tuy nhiên điều này rõ ràng không phải là cách để đi nếu mã được dành cho bất kỳ ai khác ngoài chính bạn. – ensisNoctis