Tại sao C # không cung cấp từ khóa 'bạn bè' kiểu C ++? [đóng cửa]


208

Các C ++ từ khóa bạn cho phép một class Ađể chỉ class Bnhư một người bạn của mình. Điều này cho phép Class Btruy cập vào private/ protectedthành viên của class A.

Tôi chưa bao giờ đọc bất cứ điều gì về lý do tại sao điều này bị loại khỏi C # (và VB.NET). Hầu hết các câu trả lời cho câu hỏi StackOverflow trước đó dường như đều nói rằng đây là một phần hữu ích của C ++ và có những lý do chính đáng để sử dụng nó. Theo kinh nghiệm của tôi, tôi phải đồng ý.

Một câu hỏi khác đối với tôi là thực sự hỏi làm thế nào để làm một cái gì đó tương tự như friendtrong ứng dụng C #. Mặc dù các câu trả lời thường xoay quanh các lớp lồng nhau, nhưng nó không có vẻ thanh lịch như sử dụng friendtừ khóa.

Cuốn sách Mẫu thiết kế ban đầu sử dụng nó thường xuyên trong suốt các ví dụ của nó.

Vì vậy, tóm lại, tại sao lại friendthiếu C #, và cách "thực hành tốt nhất" (hoặc cách) mô phỏng nó trong C # là gì?

(Nhân tiện, internaltừ khóa không giống nhau, nó cho phép tất cả các lớp trong toàn bộ hội đồng truy cập internalcác thành viên, trong khi friendcho phép bạn cấp cho một lớp nhất định quyền truy cập vào chính xác một lớp khác)


5
tôi cảm thấy nội bộ được bảo vệ là một sự thỏa hiệp tốt ..
Gulzar Nazim

3
Nó không quá khó hiểu phải không? Như tôi đã nói ở trên, cuốn sách GOF sử dụng nó khá thường xuyên trong các ví dụ. Đối với tôi nó không còn khó hiểu nữa.
Tro

7
Tôi chắc chắn có thể thấy các tình huống trong đó chúng có thể rất hữu ích, như đã đề cập đến Kiểm thử đơn vị. Nhưng bạn có thực sự muốn bạn bè của bạn truy cập vào tư nhân của bạn?
danswain

2
Thật dễ dàng để trả lời: C # cung cấp "nội bộ" dưới dạng công cụ sửa đổi truy cập cho phép truy cập vào tất cả các mã trong cùng một mô-đun / lắp ráp. Điều này loại bỏ sự cần thiết cho một cái gì đó như bạn bè. Trong Java, từ khóa "được bảo vệ" hoạt động truy cập wrt tương tự từ cùng một gói.
sellibitze

2
@sellibitze, tôi đề cập đến sự khác biệt với nội bộ trong câu hỏi. Vấn đề với Interanl là tất cả các lớp trong hội đồng có thể truy cập các thành viên nội bộ. Điều này phá vỡ đóng gói vì maany của các lớp này có thể không cần truy cập.
Tro

Câu trả lời:


67

Có bạn bè trong lập trình ít nhiều bị coi là "bẩn" và dễ lạm dụng. Nó phá vỡ các mối quan hệ giữa các lớp và làm suy yếu một số thuộc tính cơ bản của ngôn ngữ OO.

Điều đó đang được nói, đó là một tính năng hay và bản thân tôi đã sử dụng nó rất nhiều lần trong C ++; và cũng muốn sử dụng nó trong C #. Nhưng tôi cá là vì OOness "thuần túy" của C # (so với OOness giả của C ++) MS đã quyết định rằng vì Java không có từ khóa bạn bè nên C # không nên (chỉ đùa thôi;))

Một lưu ý nghiêm trọng: nội bộ không tốt như bạn bè nhưng nó hoàn thành công việc. Hãy nhớ rằng rất hiếm khi bạn phân phối mã của mình cho các nhà phát triển bên thứ 3 không thông qua DLL; Vì vậy, miễn là bạn và nhóm của bạn biết về các lớp bên trong và việc sử dụng chúng, bạn sẽ ổn.

EDIT Hãy để tôi làm rõ cách từ khóa bạn bè làm suy yếu OOP.

Các biến và phương thức riêng tư và được bảo vệ có lẽ là một trong những phần quan trọng nhất của OOP. Ý tưởng rằng các đối tượng có thể chứa dữ liệu hoặc logic mà chỉ họ có thể sử dụng cho phép bạn viết việc thực hiện chức năng độc lập với môi trường của bạn - và môi trường của bạn có thể thay đổi thông tin trạng thái không phù hợp để xử lý. Bằng cách sử dụng người bạn, bạn đang ghép các triển khai của hai lớp lại với nhau - điều đó còn tệ hơn nhiều nếu bạn chỉ ghép giao diện của chúng.


167
C # 'nội bộ' thực sự gây tổn hại cho việc đóng gói nhiều hơn C ++ 'bạn'. Với "tình bạn", chủ sở hữu rõ ràng cho phép bất cứ ai họ muốn. "Nội bộ" là một khái niệm kết thúc mở.
Nemanja Trifunovic

21
Anders nên được khen ngợi vì những đặc điểm mà anh ấy bỏ qua không phải là những tính năng anh ấy đưa vào.
Mehrdad Afshari

21
Mặc dù vậy, tôi không đồng ý rằng nội bộ của C # làm tổn thương sự bao bọc. Ngược lại theo quan điểm của tôi. Bạn có thể sử dụng nó để tạo ra một hệ sinh thái đóng gói trong một hội đồng. Một số đối tượng có thể chia sẻ các loại nội bộ trong hệ sinh thái này không được tiếp xúc với phần còn lại của ứng dụng. Tôi sử dụng các thủ thuật lắp ráp "bạn bè" để tạo các cụm thử nghiệm đơn vị cho các đối tượng này.
Quibbledome

26
Điều này không có ý nghĩa. friendkhông để các lớp hoặc hàm tùy ý truy cập các thành viên riêng. Nó cho phép các chức năng hoặc các lớp cụ thể được đánh dấu là bạn bè làm như vậy. Lớp sở hữu thành viên tư nhân vẫn kiểm soát quyền truy cập vào nó 100%. Bạn cũng có thể tranh luận rằng các phương pháp thành viên công cộng. Cả hai đều đảm bảo rằng các phương thức được liệt kê cụ thể trong lớp được cấp quyền truy cập cho các thành viên riêng của lớp.
jalf

8
Tôi nghĩ hoàn toàn ngược lại. Nếu một hội đồng có 50 lớp, nếu 3 trong số các lớp đó sử dụng một số lớp tiện ích, thì hôm nay, tùy chọn duy nhất của bạn là đánh dấu lớp tiện ích đó là "nội bộ". Khi làm như vậy, bạn không chỉ làm cho nó có sẵn để sử dụng cho 3 lớp, mà còn cho các lớp còn lại trong hội đồng. Điều gì sẽ xảy ra nếu tất cả những gì bạn muốn làm là cung cấp quyền truy cập chi tiết hơn sao cho chỉ có ba lớp trong câu hỏi có quyền truy cập vào lớp tiện ích. Nó thực sự làm cho nó hướng đối tượng hơn vì nó cải thiện việc đóng gói.
bảo vệ zumalifif

104

Còn một chú ý đáng nói. Sử dụng bạn bè không phải là vi phạm đóng gói, mà trái lại, đó là về việc thực thi nó. Giống như người truy cập + trình biến đổi, toán tử quá tải, kế thừa công khai, hạ lưu, v.v. , nó thường bị sử dụng sai, nhưng điều đó không có nghĩa là từ khóa không có, hoặc tệ hơn, mục đích xấu.

Xem tin nhắn của Konrad Rudolph trong chuỗi khác hoặc nếu bạn muốn xem mục liên quan trong Câu hỏi thường gặp về C ++.


... và mục tương ứng trong FQA: yosefk.com/c++fqa/friend.html#fqa-14.2
Josh Lee

28
+1 cho " Sử dụng bạn bè không phải là vi phạm đóng gói, mà trái lại, đó là về việc thực thi nó "
Nawaz

2
Một vấn đề về ý kiến ​​ngữ nghĩa tôi nghĩ; và có lẽ liên quan đến tình huống chính xác trong câu hỏi. Tạo một lớp friendcho phép nó truy cập vào TẤT CẢ các thành viên riêng của bạn, ngay cả khi bạn chỉ cần để nó đặt một trong số họ. Có một lập luận mạnh mẽ được đưa ra là làm cho các trường có sẵn cho một lớp khác không cần chúng được tính là "đóng gói vi phạm". Có những nơi trong C ++ là cần thiết (quá tải toán tử), nhưng có một sự đánh đổi đóng gói cần được xem xét cẩn thận. Có vẻ như các nhà thiết kế C # cảm thấy rằng sự đánh đổi là không xứng đáng.
GrandOpener

3
Đóng gói là về thực thi bất biến. Khi chúng ta định nghĩa một lớp là bạn với nhau, chúng ta nói rõ ràng rằng hai lớp bị ràng buộc chặt chẽ về việc quản lý các bất biến của lớp đầu tiên. Có lớp kết bạn vẫn tốt hơn là phơi bày tất cả các thuộc tính trực tiếp (dưới dạng trường công khai) hoặc thông qua setters.
Luc Hermitte

1
Chính xác. Khi bạn muốn sử dụng bạn bè nhưng không thể, bạn buộc phải sử dụng nội bộ hoặc công khai, điều này sẽ cởi mở hơn nhiều khi bị chọc vào bởi những điều không nên. Đặc biệt là trong môi trường làm việc nhóm.
Nở vào

50

Để biết thông tin, một thứ khác có liên quan nhưng không hoàn toàn giống nhau trong .NET là [InternalsVisibleTo]cho phép một hội đồng chỉ định một hội đồng khác (chẳng hạn như một hội đồng kiểm tra đơn vị) mà (có hiệu quả) có quyền truy cập "nội bộ" vào các loại / thành viên trong lắp ráp ban đầu.


3
gợi ý tốt, vì có lẽ mọi người thường tìm kiếm bạn bè muốn tìm cách kiểm tra đơn vị với khả năng có nhiều 'kiến thức' nội bộ hơn.
SwissCoder

14

Bạn sẽ có thể thực hiện các loại tương tự mà "người bạn" được sử dụng trong C ++ bằng cách sử dụng các giao diện trong C #. Nó yêu cầu bạn xác định rõ ràng thành viên nào đang được thông qua giữa hai lớp, đây là công việc bổ sung nhưng cũng có thể làm cho mã dễ hiểu hơn.

Nếu ai đó có một ví dụ về việc sử dụng "bạn bè" hợp lý không thể mô phỏng bằng giao diện, vui lòng chia sẻ nó! Tôi muốn hiểu rõ hơn về sự khác biệt giữa C ++ và C #.


Điểm tốt. Bạn đã có một cơ hội để nhìn vào câu hỏi SO trước (hỏi bởi monoxide và liên kết ở trên ?. Tôi muốn được quan tâm như thế nào tốt nhất để giải quyết rằng trong C #?
Ash

Tôi không chắc chắn chính xác làm thế nào để làm điều đó trong C #, nhưng tôi nghĩ câu trả lời của Jon Skeet cho các điểm câu hỏi của monoxide theo đúng hướng. C # cho phép các lớp lồng nhau. Tôi thực sự đã không cố gắng viết mã và biên dịch một giải pháp. :)
Parappa

Tôi cảm thấy rằng mô hình hòa giải là một ví dụ tốt về nơi bạn bè sẽ tốt đẹp. Tôi cấu trúc lại các mẫu đơn của mình để có một hòa giải viên. Tôi muốn biểu mẫu của mình có thể gọi "phương thức" như các phương thức trong hòa giải viên, nhưng tôi không thực sự muốn các lớp khác gọi các phương thức đó là "sự kiện". Tính năng "Bạn bè" sẽ cung cấp cho biểu mẫu quyền truy cập vào các phương thức mà không cần mở nó ra cho mọi thứ khác trong cụm.
Vaccano

3
Vấn đề với cách tiếp cận giao diện thay thế 'Bạn bè' là để một đối tượng phơi bày giao diện, điều đó có nghĩa là bất kỳ ai cũng có thể truyền đối tượng đến giao diện đó và có quyền truy cập vào các phương thức! Phạm vi giao diện đến nội bộ, được bảo vệ hoặc riêng tư không hoạt động như thể nó hoạt động, bạn chỉ có thể phạm vi phương thức được đề cập! Hãy nghĩ về một người mẹ và đứa trẻ trong một trung tâm mua sắm. Công chúng là tất cả mọi người trên thế giới. Nội bộ chỉ là những người trong trung tâm mua sắm. Riêng tư chỉ là mẹ hay con. "Bạn bè" chỉ là một cái gì đó giữa mẹ và con gái.
Đánh dấu A. Donohoe

1
Đây là một: stackoverflow.com/questions/5921612/ Từ Khi tôi đến từ nền tảng C ++, việc thiếu bạn bè khiến tôi phát điên gần như hàng ngày. Trong vài năm với C #, tôi đã thực hiện hàng trăm phương thức công khai, nơi chúng có thể riêng tư và an toàn hơn, tất cả chỉ vì thiếu bạn bè. Cá nhân tôi nghĩ rằng bạn bè là điều quan trọng nhất cần được thêm vào .NET
kaalus

14

Với friendmột nhà thiết kế C ++ có quyền kiểm soát chính xác đối với những thành viên * riêng tư được tiếp xúc. Nhưng, anh ta buộc phải phơi bày mọi thành viên tư nhân.

Với internalmột nhà thiết kế C # có quyền kiểm soát chính xác đối với nhóm thành viên riêng mà anh ta tiếp xúc. Rõ ràng, anh ta có thể chỉ lộ ra một thành viên tư nhân duy nhất. Nhưng, nó sẽ được tiếp xúc với tất cả các lớp trong hội đồng.

Thông thường, một nhà thiết kế mong muốn chỉ đưa ra một vài phương thức riêng tư cho một vài lớp khác được chọn. Ví dụ, trong một mẫu nhà máy của lớp, có thể mong muốn rằng lớp C1 chỉ được khởi tạo bởi lớp CF1 của nhà máy. Do đó, lớp C1 có thể có một hàm tạo được bảo vệ và một nhà máy lớp CF1.

Như bạn có thể thấy, chúng tôi có 2 chiều dọc theo đó đóng gói có thể bị vi phạm. friendvi phạm nó dọc theo một chiều, internallàm nó dọc theo chiều khác. Cái nào là vi phạm tồi tệ hơn trong khái niệm đóng gói? Khó nói. Nhưng nó sẽ là tốt đẹp để có cả hai friendinternalcó sẵn. Hơn nữa, một bổ sung tốt cho hai loại này sẽ là loại từ khóa thứ 3, được sử dụng trên cơ sở từng thành viên (như internal) và chỉ định lớp mục tiêu (như friend).

* Để đơn giản, tôi sẽ sử dụng "riêng tư" thay vì "riêng tư và / hoặc được bảo vệ".

- Nick


13

Trên thực tế, C # cho khả năng có được hành vi tương tự theo cách OOP thuần túy mà không cần từ đặc biệt - đó là giao diện riêng tư.

Theo như câu hỏi C # tương đương với bạn bè là gì? đã được đánh dấu là trùng lặp với bài viết này và không ai ở đó đề xuất thực hiện tốt - tôi sẽ hiển thị câu trả lời cho cả hai câu hỏi ở đây.

Ý tưởng chính được lấy từ đây: Giao diện riêng là gì?

Giả sử, chúng ta cần một số lớp có thể quản lý các thể hiện của các lớp khác và gọi một số phương thức đặc biệt trên chúng. Chúng tôi không muốn cung cấp khả năng gọi phương thức này cho bất kỳ lớp nào khác. Điều này hoàn toàn giống với những gì người bạn từ khóa c ++ làm trong thế giới c ++.

Tôi nghĩ ví dụ điển hình trong thực tế thực tế có thể là mẫu Máy trạng thái đầy đủ trong đó một số bộ điều khiển cập nhật đối tượng trạng thái hiện tại và chuyển sang đối tượng trạng thái khác khi cần thiết.

Bạn có thể:

  • Cách dễ nhất và tồi tệ nhất để công khai phương thức Update () - hy vọng mọi người hiểu tại sao nó xấu.
  • Cách tiếp theo là đánh dấu nó là nội bộ. Nó đủ tốt nếu bạn đặt các lớp của mình vào một hội đồng khác nhưng thậm chí sau đó mỗi lớp trong hội đồng đó có thể gọi từng phương thức bên trong.
  • Sử dụng giao diện riêng tư / được bảo vệ - và tôi đã làm theo cách này.

Bộ điều khiển.cs

public class Controller
{
    private interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() {  }
    }

    public Controller()
    {
        //it's only way call Update is to cast obj to IState
        IState obj = new StateBase();
        obj.Update();
    }
}

Chương trình.cs

class Program
{
    static void Main(string[] args)
    {
        //it's impossible to write Controller.IState p = new Controller.StateBase();
        //Controller.IState is hidden
        var p = new Controller.StateBase();
        //p.Update(); //is not accessible
    }
}

Vâng, những gì về thừa kế?

Chúng ta cần sử dụng kỹ thuật được mô tả trong Vì việc triển khai thành viên giao diện rõ ràng không thể được khai báo ảo và đánh dấu IState là được bảo vệ để cung cấp khả năng xuất phát từ Trình điều khiển.

Bộ điều khiển.cs

public class Controller
{
    protected interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() { OnUpdate(); }
        protected virtual void OnUpdate()
        {
            Console.WriteLine("StateBase.OnUpdate()");
        }
    }

    public Controller()
    {
        IState obj = new PlayerIdleState();
        obj.Update();
    }
}

PlayerIdleState.cs

public class PlayerIdleState: Controller.StateBase
{
    protected override void OnUpdate()
    {
        base.OnUpdate();
        Console.WriteLine("PlayerIdleState.OnUpdate()");
    }
}

Và cuối cùng là ví dụ về cách kiểm tra lớp kế thừa ném Trình điều khiển : ControllerTest.cs

class ControllerTest: Controller
{
    public ControllerTest()
    {
        IState testObj = new PlayerIdleState();
        testObj.Update();
    }
}

Hy vọng tôi bao gồm tất cả các trường hợp và câu trả lời của tôi là hữu ích.


Đây là một câu trả lời tuyệt vời cần nhiều upvote.
KthProg

@max_cn Đây là một giải pháp thực sự tốt, nhưng tôi không thể thấy một cách để làm việc này với các khung mô phỏng để thử nghiệm đơn vị. Bất kỳ đề xuất?
Steve Land

Tôi hơi bối rối. Nếu tôi muốn một lớp bên ngoài có thể gọi Cập nhật trong ví dụ đầu tiên của bạn (giao diện riêng), tôi sẽ sửa đổi Trình điều khiển như thế nào để cho phép điều đó xảy ra?
AnthonyMonterrosa

@Steve Land, bạn có thể sử dụng quyền thừa kế như trong ControllerTest.cs đã được hiển thị. Thêm bất kỳ phương thức công khai cần thiết nào trong một lớp kiểm tra dẫn xuất và bạn sẽ có quyền truy cập cho tất cả các nhân viên được bảo vệ mà bạn có trong lớp cơ sở.
max_cn

@AnthonyMonterrosa, chúng tôi đang ẩn Cập nhật từ TẤT CẢ các tài nguyên bên ngoài, vậy tại sao bạn muốn chia sẻ nó với ai đó;)? Chắc chắn bạn có thể thêm một số phương thức công khai để truy cập các thành viên riêng tư nhưng nó sẽ giống như một cửa hậu cho bất kỳ lớp nào khác. Dù sao, nếu bạn cần nó cho mục đích thử nghiệm - hãy sử dụng tính kế thừa để tạo ra thứ gì đó giống như lớp ControllerTest để thực hiện bất kỳ trường hợp thử nghiệm nào ở đó.
max_cn

9

Friend cực kỳ hữu ích khi viết bài kiểm tra đơn vị.

Mặc dù có chi phí gây ô nhiễm một chút khi khai báo lớp của bạn, nhưng đây cũng là một lời nhắc nhở do trình biên dịch thực thi về những bài kiểm tra thực sự có thể quan tâm đến trạng thái bên trong của lớp.

Một thành ngữ rất hữu ích và rõ ràng mà tôi đã tìm thấy là khi tôi có các lớp học tại nhà máy, biến chúng thành bạn bè của các vật phẩm chúng tạo ra có một hàm tạo được bảo vệ. Cụ thể hơn, đó là khi tôi có một nhà máy duy nhất chịu trách nhiệm tạo các đối tượng kết xuất phù hợp cho các đối tượng người viết báo cáo, kết xuất với một môi trường nhất định. Trong trường hợp này, bạn có một điểm kiến ​​thức duy nhất về mối quan hệ giữa các lớp người viết báo cáo (những thứ như khối hình ảnh, dải bố cục, tiêu đề trang, v.v.) và các đối tượng kết xuất phù hợp của chúng.


Tôi sẽ đưa ra nhận xét về "người bạn" hữu ích cho các lớp học tại nhà máy, nhưng tôi rất vui khi thấy ai đó đã thực hiện nó.
Edward Robertson

9

C # bị thiếu từ khóa "bạn bè" vì lý do tương tự sự phá hủy xác định của nó. Thay đổi quy ước làm cho mọi người cảm thấy thông minh, như thể những cách mới của họ vượt trội hơn những cách cũ của người khác. Đó là tất cả về niềm tự hào.

Nói rằng "các lớp bạn là xấu" cũng thiển cận như các tuyên bố không đủ tiêu chuẩn khác như "không sử dụng gotos" hoặc "Linux tốt hơn Windows".

Từ khóa "bạn bè" kết hợp với một lớp proxy là một cách tuyệt vời để chỉ hiển thị một số phần nhất định của một lớp cho các lớp khác cụ thể. Một lớp proxy có thể hoạt động như một rào cản đáng tin cậy đối với tất cả các lớp khác. "Công khai" không cho phép bất kỳ nhắm mục tiêu nào như vậy và việc sử dụng "được bảo vệ" để có được hiệu ứng với quyền thừa kế là khó xử nếu thực sự không có mối quan hệ "là" khái niệm.


C # bị thiếu sự phá hủy xác định vì nó chậm hơn so với thu gom rác. Sự phá hủy mang tính quyết định cần có thời gian cho mọi đối tượng được tạo ra, trong khi việc thu gom rác chỉ mất thời gian cho các đối tượng hiện đang sống. Trong hầu hết các trường hợp, điều này nhanh hơn và các đối tượng có thời gian tồn tại càng ngắn trong hệ thống, bộ sưu tập rác tương đối nhanh hơn.
Edward Robertson

1
@Edward Robertson Sự phá hủy xác định không mất bất kỳ thời gian nào trừ khi có một hàm hủy được xác định. Nhưng trong trường hợp này, bộ sưu tập rác tồi tệ hơn nhiều, bởi vì sau đó bạn cần sử dụng một bộ hoàn thiện, nó chậm hơn và không phổ biến.
kaalus

1
... Và bộ nhớ không phải là loại tài nguyên duy nhất. Mọi người thường hành động như thể đó là sự thật.
cubuspl42

8

Bạn có thể gần gũi với "người bạn" C ++ với từ khóa "nội bộ" C # .


3
Gần, nhưng không hoàn toàn ở đó. Tôi cho rằng nó phụ thuộc vào cách bạn cấu trúc các hội đồng / lớp của bạn, nhưng sẽ thanh lịch hơn khi giảm thiểu số lượng các lớp được truy cập bằng cách sử dụng bạn bè. Ví dụ FOr trong tập hợp tiện ích / trình trợ giúp, không phải tất cả các lớp nên có quyền truy cập vào các thành viên nội bộ.
Tro

3
@Ash: Bạn cần phải làm quen với nó nhưng một khi bạn thấy các hội đồng là khối xây dựng cơ bản của các ứng dụng của mình, internalsẽ có ý nghĩa lớn hơn và cung cấp một sự đánh đổi tuyệt vời giữa đóng gói và tiện ích. Mặc dù vậy, truy cập mặc định của Java thậm chí còn tốt hơn.
Konrad Rudolph

7

Đây thực sự không phải là một vấn đề với C #. Đó là một hạn chế cơ bản trong IL. C # bị giới hạn bởi điều này, cũng như bất kỳ ngôn ngữ .Net nào khác tìm cách xác minh được. Giới hạn này cũng bao gồm các lớp được quản lý được xác định trong C ++ / CLI ( Spec phần 20.5 ).

Điều đó đang được nói tôi nghĩ rằng Nelson có một lời giải thích tốt về lý do tại sao đây là một điều xấu.


7
-1. friendkhông phải là một điều xấu; trái lại, nó tốt hơn nhiều internal. Và lời giải thích của Nelson là xấu và không chính xác.
Nawaz

4

Ngừng làm cho lý do cho giới hạn này. bạn là xấu, nhưng nội bộ là tốt? chúng giống nhau, chỉ có người bạn đó cho bạn quyền kiểm soát chính xác hơn về người được phép truy cập và ai không.

Đây là để thực thi mô hình đóng gói? Vì vậy, bạn phải viết phương thức accessor và bây giờ những gì? Làm thế nào bạn có nghĩa vụ ngăn chặn tất cả mọi người (ngoại trừ các phương thức của lớp B) gọi các phương thức này? bạn không thể, bởi vì bạn cũng không thể kiểm soát điều này, vì thiếu "người bạn".

Không có ngôn ngữ lập trình là hoàn hảo. C # là một trong những ngôn ngữ tốt nhất tôi từng thấy, nhưng kiếm cớ ngớ ngẩn cho các tính năng bị thiếu không giúp được ai. Trong C ++, tôi bỏ lỡ hệ thống sự kiện / ủy nhiệm dễ dàng, phản xạ (+ khử tự động / tuần tự hóa) và foreach, nhưng trong C # tôi nhớ quá tải toán tử (vâng, cứ nói với tôi rằng bạn không cần nó), các tham số mặc định, một hằng số không thể tránh được, thừa kế nhiều lần (vâng, hãy nói với tôi rằng bạn không cần nó và giao diện là một sự thay thế đầy đủ) và khả năng quyết định xóa một cá thể khỏi bộ nhớ (không, điều này không tệ lắm trừ khi bạn là một tinkerer)


Trong C #, bạn có thể quá tải hầu hết các toán tử. Bạn không thể quá tải toán tử gán, nhưng mặt khác, bạn có thể ghi đè ||/ &&trong khi giữ cho chúng ngắn mạch. Các tham số mặc định đã được thêm vào trong C # 4.
CodeInChaos

2

Có InternalsVisibleToAttribution kể từ .Net 3 nhưng tôi nghi ngờ họ chỉ thêm nó để phục vụ cho việc lắp ráp thử nghiệm sau khi tăng thử nghiệm đơn vị. Tôi không thể thấy nhiều lý do khác để sử dụng nó.

Nó hoạt động ở cấp độ lắp ráp nhưng nó thực hiện công việc mà bên trong không; đó là, nơi bạn muốn phân phối một hội đồng nhưng muốn một hội đồng không phân phối khác có quyền truy cập đặc quyền vào nó.

Hoàn toàn đúng, họ yêu cầu hội đồng bạn phải được khóa mạnh để tránh ai đó tạo ra một người bạn giả vờ bên cạnh hội đồng được bảo vệ của bạn.


2

Tôi đã đọc nhiều bình luận thông minh về từ khóa "bạn bè" và tôi đồng ý rằng đó là điều hữu ích, nhưng tôi nghĩ từ khóa "nội bộ" nào ít hữu ích hơn và cả hai vẫn không tốt cho lập trình OO thuần túy.

Những gì chúng ta có? (nói về "bạn bè" tôi cũng nói về "nội bộ")

  • việc sử dụng "người bạn" làm cho mã kém tinh khiết hơn liên quan đến oo?
  • Đúng;

  • không sử dụng "bạn bè" làm cho mã tốt hơn?

  • không, chúng ta vẫn cần tạo một số mối quan hệ riêng tư giữa các lớp và chúng ta chỉ có thể làm điều đó nếu chúng ta phá vỡ sự đóng gói đẹp đẽ của mình, vì vậy nó cũng không tốt, tôi có thể nói điều gì còn tệ hơn cả việc sử dụng "bạn bè".

Sử dụng kết bạn làm cho một số vấn đề cục bộ, không sử dụng nó gây ra vấn đề cho người dùng mã thư viện.

giải pháp tốt chung cho ngôn ngữ lập trình tôi thấy như thế này:

// c++ style
class Foo {
  public_for Bar:
    void addBar(Bar *bar) { }
  public:
  private:
  protected:
};

// c#
class Foo {
    public_for Bar void addBar(Bar bar) { }
}

Bạn nghĩ gì về nó? Tôi nghĩ rằng nó là giải pháp hướng đối tượng phổ biến và thuần túy nhất. Bạn có thể mở truy cập bất kỳ phương thức nào bạn chọn vào bất kỳ lớp nào bạn muốn.


Tôi không phải là một lính gác OOP, nhưng có vẻ như nó giải quyết được sự kìm kẹp của mọi người đối với bạn bè và nội bộ. Nhưng tôi sẽ đề nghị sử dụng một thuộc tính để tránh việc mở rộng cú pháp của C #: [PublicFor Bar] void addBar (Thanh bar) {} ... Không phải là tôi chắc chắn điều đó có ý nghĩa; Tôi không biết các thuộc tính hoạt động như thế nào hoặc liệu chúng có thể nghịch ngợm với khả năng truy cập hay không (chúng làm rất nhiều thứ "ma thuật" khác).
Grault

2

Tôi sẽ chỉ trả lời câu hỏi "Làm thế nào".

Có rất nhiều câu trả lời ở đây, tuy nhiên tôi muốn đề xuất loại "mẫu thiết kế" để đạt được tính năng đó. Tôi sẽ sử dụng cơ chế ngôn ngữ đơn giản, bao gồm:

  • Giao diện
  • Lớp lồng

Ví dụ chúng tôi có 2 lớp chính: Sinh viên và Đại học. Sinh viên có GPA mà chỉ trường đại học mới được phép truy cập. Đây là mã:

public interface IStudentFriend
{
    Student Stu { get; set; }
    double GetGPS();
}

public class Student
{
    // this is private member that I expose to friend only
    double GPS { get; set; }
    public string Name { get; set; }

    PrivateData privateData;

    public Student(string name, double gps) => (GPS, Name, privateData) = (gps, name, new PrivateData(this);

    // No one can instantiate this class, but Student
    // Calling it is possible via the IStudentFriend interface
    class PrivateData : IStudentFriend
    {
        public Student Stu { get; set; }

        public PrivateData(Student stu) => Stu = stu;
        public double GetGPS() => Stu.GPS;
    }

    // This is how I "mark" who is Students "friend"
    public void RegisterFriend(University friend) => friend.Register(privateData);
}

public class University
{
    var studentsFriends = new List<IStudentFriend>();

    public void Register(IStudentFriend friendMethod) => studentsFriends.Add(friendMethod);

    public void PrintAllStudentsGPS()
    {
        foreach (var stu in studentsFriends)
            Console.WriteLine($"{stu.Stu.Name}: stu.GetGPS()");
    }
}

public static void Main(string[] args)
{
    var Technion = new University();
    var Alex     = new Student("Alex", 98);
    var Jo       = new Student("Jo", 91);

    Alex.RegisterFriend(Technion);
    Jo.RegisterFriend(Technion);
    Technion.PrintAllStudentsGPS();

    Console.ReadLine();
}

1

Tôi nghi ngờ nó có liên quan đến mô hình biên dịch C # - xây dựng IL biên dịch JIT khi chạy. tức là: cùng một lý do mà thuốc generic C # khác về cơ bản với thuốc generic C ++.


Tôi nghĩ trình biên dịch có thể hỗ trợ nó một cách hợp lý dễ dàng ít nhất là giữa các lớp trong một hội đồng. Chỉ là vấn đề kiểm tra truy cập thư giãn cho lớp bạn bè trên lớp mục tiêu. Bạn bè giữa các lớp trong các hội đồng bên ngoài có thể cần những thay đổi cơ bản hơn đối với CLR.
Tro

1

bạn có thể giữ riêng tư và sử dụng chức năng phản chiếu để gọi các chức năng. Khung kiểm tra có thể làm điều này nếu bạn yêu cầu nó kiểm tra một chức năng riêng tư


Trừ khi bạn đang làm việc trên một ứng dụng Silverlight.
kaalus

1

Tôi thường xuyên sử dụng bạn bè và tôi không nghĩ đó là vi phạm OOP hoặc là dấu hiệu của bất kỳ lỗi thiết kế nào. Có một số nơi mà nó là phương tiện hiệu quả nhất cho đến cuối thích hợp với số lượng mã ít nhất.

Một ví dụ cụ thể là khi tạo các cụm giao diện cung cấp giao diện truyền thông cho một số phần mềm khác. Nói chung, có một vài lớp hạng nặng xử lý sự phức tạp của giao thức và đặc thù ngang hàng, và cung cấp một mô hình kết nối / đọc / ghi / chuyển tiếp / ngắt kết nối tương đối đơn giản liên quan đến việc gửi tin nhắn và thông báo giữa ứng dụng khách và lắp ráp. Những thông điệp / thông báo cần phải được gói trong các lớp. Các thuộc tính nói chung cần được thao tác bởi phần mềm giao thức vì nó là tác giả của chúng, nhưng rất nhiều thứ phải duy trì ở chế độ chỉ đọc với thế giới bên ngoài.

Thật là ngớ ngẩn khi tuyên bố rằng đó là vi phạm OOP đối với lớp giao thức / "người tạo" để có quyền truy cập mật thiết vào tất cả các lớp được tạo - lớp người tạo đã phải xử lý từng bit dữ liệu trên đường đi. Điều tôi thấy quan trọng nhất là giảm thiểu tất cả các dòng mã bổ sung của BS mà mô hình "OOP for OOP's Sake" thường dẫn đến. Spaghetti thêm chỉ làm cho nhiều lỗi hơn.

Mọi người có biết rằng bạn có thể áp dụng từ khóa nội bộ ở cấp độ thuộc tính, thuộc tính và phương thức không? Nó không chỉ dành cho khai báo lớp cấp cao nhất (mặc dù hầu hết các ví dụ dường như cho thấy điều đó.)

Nếu bạn có lớp C ++ sử dụng từ khóa friend và muốn mô phỏng nó trong lớp C #: 1. khai báo lớp C # công khai 2. khai báo tất cả các thuộc tính / thuộc tính / phương thức được bảo vệ trong C ++ và do đó bạn bè có thể truy cập được nội bộ trong C # 3. tạo các thuộc tính chỉ đọc để truy cập công khai vào tất cả các thuộc tính và thuộc tính bên trong

Tôi đồng ý rằng nó không giống 100% với bạn bè và kiểm tra đơn vị là một ví dụ rất có giá trị về nhu cầu của một cái gì đó giống như bạn bè (như mã đăng nhập phân tích giao thức). Tuy nhiên, bên trong cung cấp mức độ hiển thị cho các lớp bạn muốn có phơi sáng và [InternalVisibleTo ()] xử lý phần còn lại - có vẻ như nó được sinh ra dành riêng cho thử nghiệm đơn vị.

Theo như bạn bè "trở nên tốt hơn bởi vì bạn có thể kiểm soát rõ ràng các lớp nào có quyền truy cập" - cái quái gì là một nhóm các lớp ác nghi ngờ đang làm trong cùng một hội đồng ở nơi đầu tiên? Phân vùng hội đồng của bạn!


1

Tình bạn có thể được mô phỏng bằng cách tách giao diện và triển khai. Ý tưởng là: " Yêu cầu một thể hiện cụ thể nhưng hạn chế quyền truy cập xây dựng của thể hiện đó ".

Ví dụ

interface IFriend { }

class Friend : IFriend
{
    public static IFriend New() { return new Friend(); }
    private Friend() { }

    private void CallTheBody() 
    {  
        var body = new Body();
        body.ItsMeYourFriend(this);
    }
}

class Body
{ 
    public void ItsMeYourFriend(Friend onlyAccess) { }
}

Mặc dù thực tế ItsMeYourFriend()là chỉ có Friendlớp công khai mới có thể truy cập nó, vì không ai khác có thể có được một thể hiện cụ thể của Friendlớp. Nó có một nhà xây dựng tư nhân, trong khi nhà máyNew() phương thức trả về một giao diện.

Xem bài viết của tôi Bạn bè và thành viên giao diện nội bộ miễn phí với mã hóa thành giao diện để biết chi tiết.


Đáng buồn là tình bạn không thể được mô phỏng theo bất kỳ cách nào có thể. Xem câu hỏi này: stackoverflow.com/questions/5921612/ từ
kaalus

1

Một số người cho rằng mọi thứ có thể vượt khỏi tầm kiểm soát bằng cách sử dụng bạn bè. Tôi đồng ý, nhưng điều đó không làm giảm tính hữu dụng của nó. Tôi không chắc chắn rằng người bạn đó nhất thiết làm tổn thương mô hình OO hơn là làm cho tất cả các thành viên trong lớp của bạn công khai. Chắc chắn ngôn ngữ sẽ cho phép bạn công khai tất cả các thành viên của mình, nhưng đó là một lập trình viên có kỷ luật, tránh kiểu mẫu thiết kế đó. Tương tự như vậy, một lập trình viên có kỷ luật sẽ bảo lưu việc sử dụng bạn bè cho các trường hợp cụ thể trong trường hợp có ý nghĩa. Tôi cảm thấy nội bộ phơi bày quá nhiều trong một số trường hợp. Tại sao để lộ một lớp hoặc phương thức cho mọi thứ trong hội đồng?

Tôi có một trang ASP.NET kế thừa trang cơ sở của riêng tôi, đến lượt nó kế thừa System.Web.UI.Page. Trong trang này, tôi có một số mã xử lý báo cáo lỗi của người dùng cuối cho ứng dụng theo phương thức được bảo vệ

ReportError("Uh Oh!");

Bây giờ, tôi có một điều khiển người dùng có trong trang. Tôi muốn kiểm soát người dùng có thể gọi các phương thức báo cáo lỗi trong trang.

MyBasePage bp = Page as MyBasePage;
bp.ReportError("Uh Oh");

Nó không thể làm điều đó nếu phương thức ReportError được bảo vệ. Tôi có thể làm cho nó bên trong, nhưng nó được tiếp xúc với bất kỳ mã nào trong hội đồng. Tôi chỉ muốn nó tiếp xúc với các thành phần UI là một phần của trang hiện tại (bao gồm các điều khiển con). Cụ thể hơn, tôi muốn lớp điều khiển cơ sở của mình xác định chính xác các phương thức báo cáo lỗi và chỉ cần gọi các phương thức trong trang cơ sở.

protected void ReportError(string str) {
    MyBasePage bp = Page as MyBasePage;
    bp.ReportError(str);
}

Tôi tin rằng một cái gì đó như bạn bè có thể hữu ích và được triển khai bằng ngôn ngữ mà không làm cho ngôn ngữ bớt "OO" như, có thể là thuộc tính, để bạn có thể có các lớp hoặc phương thức làm bạn với các lớp hoặc phương thức cụ thể, cho phép nhà phát triển cung cấp truy cập cụ thể. Có lẽ một cái gì đó như ... (mã giả)

[Friend(B)]
class A {

    AMethod() { }

    [Friend(C)]
    ACMethod() { }
}

class B {
    BMethod() { A.AMethod() }
}

class C {
    CMethod() { A.ACMethod() }
}

Trong trường hợp ví dụ trước của tôi có lẽ có một cái gì đó như sau (người ta có thể tranh luận về ngữ nghĩa, nhưng tôi chỉ đang cố gắng để có ý tưởng xuyên suốt):

class BasePage {

    [Friend(BaseControl.ReportError(string)]
    protected void ReportError(string str) { }
}

class BaseControl {
    protected void ReportError(string str) {
        MyBasePage bp = Page as MyBasePage;
        bp.ReportError(str);
    }
}

Như tôi thấy, khái niệm bạn bè không có rủi ro nào hơn là làm cho mọi thứ trở nên công khai, hoặc tạo các phương thức hoặc thuộc tính công khai để truy cập các thành viên. Nếu bất cứ điều gì bạn bè cho phép mức độ chi tiết khác của khả năng truy cập dữ liệu và cho phép bạn thu hẹp khả năng truy cập đó thay vì mở rộng nó với nội bộ hoặc công khai.


0

Nếu bạn đang làm việc với C ++ và bạn thấy mình sử dụng từ khóa bạn bè, thì đó là một dấu hiệu rất rõ ràng, rằng bạn có vấn đề về thiết kế, bởi vì tại sao một lớp cần phải truy cập vào các thành viên riêng của lớp khác ??


Tôi đồng ý. Tôi không bao giờ cần từ khóa bạn bè. Nó thường được sử dụng để giải quyết các vấn đề bảo trì phức tạp.
Edouard A.

7
Toán tử quá tải, có ai không ??
Chúng tôi là tất cả Monica

3
Đã bao nhiêu lần bạn tìm thấy một trường hợp tốt cho nhà điều hành quá tải nghiêm trọng?
bashmohandes

8
Tôi sẽ cung cấp cho bạn một trường hợp rất đơn giản, hoàn toàn gỡ rối nhận xét về 'vấn đề thiết kế' của bạn. Cha mẹ-Con. Một phụ huynh sở hữu con cái của nó. Một đứa trẻ trong bộ sưu tập 'Trẻ em' của cha mẹ được sở hữu bởi cha mẹ đó. Bất cứ ai cũng có thể thiết lập tài sản của Phụ huynh về Trẻ em. Nó cần được chỉ định khi và chỉ khi đứa trẻ được thêm vào bộ sưu tập Trẻ em của cha mẹ. Nếu nó công khai, bất cứ ai cũng có thể thay đổi nó. Nội bộ: bất cứ ai trong hội đồng đó. Riêng tư? Không một ai. Làm cho setter trở thành một người bạn để cha mẹ loại bỏ những trường hợp đó và vẫn giữ cho các lớp của bạn tách biệt, nhưng liên quan chặt chẽ. Huzzah !!
Đánh dấu A. Donohoe

2
Có rất nhiều trường hợp như vậy, chẳng hạn như hầu hết các mẫu quản lý / quản lý cơ bản. Có một câu hỏi khác về SO và không ai tìm ra cách để làm điều đó mà không có bạn bè. Không chắc chắn điều gì khiến mọi người nghĩ rằng một lớp "sẽ luôn luôn đủ". Đôi khi nhiều hơn một lớp cần phải làm việc cùng nhau.
kaalus

0

Bsd

Nó đã được tuyên bố rằng, bạn bè làm tổn thương OOness thuần túy. Mà tôi đồng ý.

Nó cũng được tuyên bố rằng bạn bè giúp đóng gói, mà tôi cũng đồng ý.

Tôi nghĩ rằng tình bạn nên được thêm vào phương pháp OO, nhưng không hoàn toàn như trong C ++. Tôi muốn có một số trường / phương thức mà lớp bạn bè của tôi có thể truy cập, nhưng tôi KHÔNG muốn họ truy cập TẤT CẢ các trường / phương thức của tôi. Như trong cuộc sống thực, tôi đã để bạn bè truy cập vào tủ lạnh cá nhân của mình nhưng tôi không cho phép họ truy cập vào tài khoản ngân hàng của mình.

Người ta có thể thực hiện điều đó như sau

    class C1
    {
        private void MyMethod(double x, int i)
        {
            // some code
        }
        // the friend class would be able to call myMethod
        public void MyMethod(FriendClass F, double x, int i)
        {
            this.MyMethod(x, i);
        }
        //my friend class wouldn't have access to this method 
        private void MyVeryPrivateMethod(string s)
        {
            // some code
        }
    }
    class FriendClass
    {
        public void SomeMethod()
        {
            C1 c = new C1();
            c.MyMethod(this, 5.5, 3);
        }
    }

Điều đó tất nhiên sẽ tạo ra một cảnh báo trình biên dịch, và sẽ làm tổn thương intellisense. Nhưng nó sẽ làm việc.

Bên cạnh đó, tôi nghĩ rằng một lập trình viên tự tin nên làm đơn vị thử nghiệm mà không cần truy cập các thành viên riêng. Điều này khá ngoài phạm vi, nhưng hãy thử đọc về TDD. tuy nhiên, nếu bạn vẫn muốn làm như vậy (có c ++ như bạn bè) hãy thử một cái gì đó như

#if UNIT_TESTING
        public
#else
        private
#endif
            double x;

do đó, bạn viết tất cả mã của mình mà không xác định UNIT_TESTING và khi bạn muốn thực hiện kiểm tra đơn vị, bạn thêm #define UNIT_TESTING vào dòng đầu tiên của tệp (và viết tất cả mã thực hiện kiểm tra đơn vị trong #if UNIT_TESTING). Điều đó nên được xử lý cẩn thận.

Vì tôi nghĩ rằng kiểm tra đơn vị là một ví dụ tồi cho việc sử dụng bạn bè, tôi sẽ đưa ra một ví dụ tại sao tôi nghĩ bạn bè có thể tốt. Giả sử bạn có một hệ thống phá vỡ (lớp). Với việc sử dụng, hệ thống phá vỡ bị hao mòn và cần được cải tạo. Bây giờ, bạn muốn rằng chỉ có một thợ máy được cấp phép sẽ sửa nó. Để làm cho ví dụ ít tầm thường hơn, tôi nói rằng thợ máy sẽ sử dụng tuốc nơ vít (riêng tư) của mình để sửa nó. Đó là lý do tại sao lớp cơ khí nên là bạn của lớp breakSystem.


2
Tham số FriendClass đó không đóng vai trò kiểm soát truy cập: bạn có thể gọi c.MyMethod((FriendClass) null, 5.5, 3)từ bất kỳ lớp nào.
Jesse McGrew

0

Tình bạn cũng có thể được mô phỏng bằng cách sử dụng "tác nhân" - một số lớp bên trong. Xem xét ví dụ sau:

public class A // Class that contains private members
{
  private class Accessor : B.BAgent // Implement accessor part of agent.
  {
    private A instance; // A instance for access to non-static members.
    static Accessor() 
    { // Init static accessors.
      B.BAgent.ABuilder = Builder;
      B.BAgent.PrivateStaticAccessor = StaticAccessor;
    }
    // Init non-static accessors.
    internal override void PrivateMethodAccessor() { instance.SomePrivateMethod(); }
    // Agent constructor for non-static members.
    internal Accessor(A instance) { this.instance = instance; }
    private static A Builder() { return new A(); }
    private static void StaticAccessor() { A.PrivateStatic(); }
  }
  public A(B friend) { B.Friendship(new A.Accessor(this)); }
  private A() { } // Private constructor that should be accessed only from B.
  private void SomePrivateMethod() { } // Private method that should be accessible from B.
  private static void PrivateStatic() { } // ... and static private method.
}
public class B
{
  // Agent for accessing A.
  internal abstract class BAgent
  {
    internal static Func<A> ABuilder; // Static members should be accessed only by delegates.
    internal static Action PrivateStaticAccessor;
    internal abstract void PrivateMethodAccessor(); // Non-static members may be accessed by delegates or by overrideable members.
  }
  internal static void Friendship(BAgent agent)
  {
    var a = BAgent.ABuilder(); // Access private constructor.
    BAgent.PrivateStaticAccessor(); // Access private static method.
    agent.PrivateMethodAccessor(); // Access private non-static member.
  }
}

Nó có thể đơn giản hơn rất nhiều khi chỉ được sử dụng để truy cập vào các thành viên tĩnh. Lợi ích cho việc thực hiện như vậy là tất cả các loại được khai báo trong phạm vi bên trong của các lớp tình bạn và, không giống như các giao diện, nó cho phép các thành viên tĩnh được truy cập.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.