Khi nào bạn nên sử dụng một lớp riêng / bên trong?


21

Để làm rõ, những gì tôi đang hỏi là

public class A{
    private/*or public*/ B b;
}

so với

public class A{
    private/*or public*/ class B{
        ....
    }
}

Tôi chắc chắn có thể nghĩ ra một số lý do để sử dụng cái này hay cái kia, nhưng những gì tôi thực sự muốn thấy là những ví dụ thuyết phục cho thấy rằng những ưu và nhược điểm không chỉ là học thuật.


2
Câu trả lời sẽ phụ thuộc vào các phương tiện được cung cấp bởi các thư viện ngôn ngữ và cốt lõi. Giả sử ngôn ngữ C #, hãy thử chạy chúng bằng StyleCop. Tôi khá chắc chắn rằng bạn sẽ nhận được một cảnh báo về việc tách lớp này ra khỏi tệp riêng của nó. Điều đó có nghĩa là đủ người tại MSFT nghĩ rằng bạn không cần sử dụng một trong những ví dụ mà bạn đã sử dụng.
Công việc

Một ví dụ thuyết phục sẽ là gì, nếu các ví dụ học thuật không đủ tốt?

@Job StyleCop sẽ chỉ đưa ra cảnh báo nếu các lớp bên trong có thể nhìn thấy từ bên ngoài. Nếu nó là riêng tư, nó hoàn toàn ok và thực sự có nhiều công dụng.
julealgon

Câu trả lời:


20

Chúng thường được sử dụng khi một lớp là một chi tiết triển khai bên trong của một lớp khác chứ không phải là một phần của giao diện bên ngoài của nó. Tôi hầu như đã xem chúng là các lớp chỉ có dữ liệu giúp cấu trúc dữ liệu bên trong sạch hơn trong các ngôn ngữ không có cú pháp riêng cho các cấu trúc kiểu c. Đôi khi chúng cũng hữu ích cho các đối tượng dẫn xuất một phương thức, tùy biến cao như xử lý sự kiện hoặc luồng xử lý.


2
Đây là cách tôi sử dụng chúng. Nếu một lớp là, từ quan điểm chức năng, không có gì ngoài một chi tiết triển khai riêng tư của một lớp khác, thì nó nên được khai báo theo cách đó, giống như một phương thức hoặc trường riêng. Không có lý do để làm ô nhiễm không gian tên với rác mà sẽ không bao giờ được sử dụng ở nơi khác.
Aaronaught

9

Giả sử bạn đang xây dựng một cái cây, một danh sách, một biểu đồ, v.v. Tại sao bạn nên phơi bày các chi tiết bên trong của một nút hoặc một ô ra thế giới bên ngoài?

Bất cứ ai sử dụng biểu đồ hoặc danh sách chỉ nên dựa vào giao diện của nó, chứ không phải triển khai nó, vì bạn có thể muốn thay đổi nó một ngày trong tương lai (ví dụ: từ triển khai dựa trên mảng sang dạng dựa trên con trỏ) và các máy khách sử dụng cấu trúc dữ liệu ( mỗi một trong số họ ) sẽ phải sửa đổi mã của họ để phù hợp với mã triển khai mới.

Thay vào đó, việc đóng gói việc thực hiện một nút hoặc một ô trong một lớp bên trong riêng cho phép bạn tự do sửa đổi việc thực hiện bất cứ lúc nào bạn cần, mà không cần các máy khách buộc phải điều chỉnh mã của chúng, miễn là giao diện của cấu trúc dữ liệu của bạn vẫn còn hoang sơ

Việc che giấu các chi tiết về việc triển khai cấu trúc dữ liệu của bạn cũng dẫn đến các lợi thế bảo mật, bởi vì nếu bạn muốn phân phối lớp của mình, bạn sẽ chỉ cung cấp tệp giao diện cùng với tệp triển khai được biên dịch và sẽ không ai biết bạn có thực sự sử dụng mảng hay con trỏ không để thực hiện, do đó bảo vệ ứng dụng của bạn khỏi một số loại khai thác hoặc, ít nhất, kiến ​​thức do không thể sử dụng sai hoặc kiểm tra mã của bạn. Ngoài các vấn đề thực tế, xin đừng đánh giá thấp thực tế rằng đó là một giải pháp cực kỳ thanh lịch trong những trường hợp như vậy.


5

Theo tôi, đó là một cảnh sát công bằng khi sử dụng các lớp được xác định bên trong cho các lớp được sử dụng ngắn gọn trong một lớp để cho phép một nhiệm vụ cụ thể.

Ví dụ: nếu bạn cần liên kết một danh sách dữ liệu bao gồm hai lớp không liên quan:

public class UiLayer
{
    public BindLists(List<A> as, List<B> bs)
    {
        var list = as.ZipWith(bs, (x, y) => new LayerListItem { AnA = x, AB = y});
        // do stuff with your new list.
    }

    private class LayerListItem
    {
        public A AnA;
        public B AB;
    }
}

Nếu lớp bên trong của bạn được sử dụng bởi một lớp khác, bạn nên đặt nó riêng biệt. Nếu lớp bên trong của bạn chứa bất kỳ logic nào, bạn nên đặt nó riêng biệt.

Về cơ bản, tôi nghĩ rằng chúng rất tuyệt khi cắm lỗ hổng vào các đối tượng dữ liệu của bạn, nhưng chúng rất khó duy trì nếu chúng phải thực sự chứa logic, vì bạn sẽ phải biết tìm chúng ở đâu nếu bạn cần thay đổi nó.


2
Giả sử C #, bạn không thể sử dụng bộ dữ liệu ở đây?
Công việc

3
@Job Chắc chắn, nhưng đôi khi tuple.ItemXcác mã làm cho không rõ ràng.
Lasse Espeholt

2
@Job yup, lasseespeholt đã đúng. Tôi muốn thấy một lớp bên trong của {chuỗi Tiêu đề; Mô tả chuỗi; } hơn một Tuple <chuỗi, chuỗi>.
Ed James

tại thời điểm đó sẽ không có ý nghĩa gì khi đưa lớp đó vào tệp riêng của nó?
Công việc

Không, không phải nếu bạn chỉ thực sự sử dụng nó để liên kết một số dữ liệu với nhau một cách nhanh chóng để đạt được một nhiệm vụ không nằm ngoài phạm vi của lớp cha. Ít nhất, không phải theo ý kiến ​​của tôi!
Ed James

4
  • Các lớp bên trong trong một số ngôn ngữ (ví dụ Java) có một ràng buộc với một đối tượng của lớp chứa chúng và có thể sử dụng các thành viên của chúng mà không đủ điều kiện. Khi có sẵn, rõ ràng hơn là thực hiện lại chúng bằng các phương tiện ngôn ngữ khác.

  • Các lớp lồng nhau trong ngôn ngữ khác (ví dụ C ++) không có sự ràng buộc như vậy. Bạn chỉ đơn giản là sử dụng điều khiển phạm vi và khả năng truy cập được cung cấp bởi lớp.


3
Thật ra Java có cả hai thứ đó.
Joachim Sauer

3

Tôi tránh các lớp bên trong trong Java vì trao đổi nóng không hoạt động với sự có mặt của các lớp bên trong. Tôi ghét điều này bởi vì tôi thực sự ghét phải thỏa hiệp mã cho những cân nhắc như vậy, nhưng những Tomcat khởi động lại cộng lại.


vấn đề này được ghi nhận ở đâu đó?
gnat

Downvoter: khẳng định của tôi không đúng?
kevin cline

1
Tôi đánh giá thấp vì câu trả lời của bạn thiếu chi tiết, không phải vì nó không chính xác. Thiếu chi tiết làm cho khẳng định của bạn khó kiểm chứng. Nếu bạn thêm nhiều hơn về vấn đề đó, tôi có thể sẽ quay trở lại upvote, bởi vì thông tin của bạn trông khá thú vị và có thể hữu ích
gnat

1
@gnat: Tôi đã thực hiện một nghiên cứu nhỏ và trong khi đây có vẻ là một sự thật nổi tiếng, tôi không tìm thấy tài liệu tham khảo chính xác về các hạn chế của Java HotSwap.
kevin cline

chúng tôi không có trên Skeptics.SE :) chỉ cần một số tài liệu tham khảo sẽ làm, nó không cần phải dứt khoát
gnat

2

Một trong những cách sử dụng cho lớp bên trong tĩnh riêng là mẫu Ghi nhớ. Bạn đặt tất cả dữ liệu cần được ghi nhớ vào lớp riêng và trả về từ một số hàm dưới dạng Object. Không ai ở bên ngoài không thể (không có phản xạ, khử lưu huỳnh, kiểm tra bộ nhớ ...) nhìn vào nó / sửa đổi nó, nhưng anh ta có thể đưa nó trở lại lớp của bạn và nó có thể khôi phục trạng thái của nó.


2

Một lý do để sử dụng lớp bên trong riêng có thể là do API bạn đang sử dụng yêu cầu sự kế thừa từ một lớp cụ thể, nhưng bạn không muốn xuất kiến ​​thức của lớp đó cho người dùng của lớp bên ngoài của bạn. Ví dụ:

// async_op.h -- someone else's api.
struct callback
{
    virtual ~callback(){}
    virtual void fire() = 0;
};

void do_my_async_op(callback *p);



// caller.cpp -- my api
class caller
{
private :
    struct caller_inner : callback
    {
        caller_inner(caller &self) : self_(self) {}
        void fire() { self_.do_callback(); }
        caller &self_;
    };

    void do_callback()
    {
        // Real callback
    }

    caller_inner inner_;

public :
    caller() : inner_(*this) {}

    void do_op()
    {
        do_my_async_op(&inner_);
    }
};

Trong trường hợp này, do_my_async_op yêu cầu một đối tượng kiểu gọi lại được truyền cho nó. Cuộc gọi lại này có chữ ký chức năng thành viên công cộng mà API sử dụng.

Khi do_op () được gọi từ lớp ngoài_ nó sẽ sử dụng một thể hiện của lớp bên trong riêng, kế thừa từ lớp gọi lại được yêu cầu thay vì một con trỏ tới chính nó. Lớp bên ngoài có một tham chiếu đến lớp bên ngoài với mục đích đơn giản là chuyển cuộc gọi lại vào hàm thành viên do_callback riêng của lớp bên ngoài .

Lợi ích của việc này là bạn chắc chắn rằng không ai khác có thể gọi hàm thành viên "fire ()" công khai.


2

Theo Jon Skeet trong C # in Depth, sử dụng một lớp lồng là cách duy nhất để thực hiện một singleton hoàn toàn lười biếng chủ đề (xem Phiên bản thứ năm) (cho đến .NET 4).


1
đó là thành ngữ Khởi tạo theo yêu cầu của chủ sở hữu , trong Java, nó cũng yêu cầu lớp bên trong tĩnh riêng. Nó được đề xuất trong Câu hỏi thường gặp của JMM , bởi Brian Goetz trong JCiP 16.4 và Joshua Bloch trong JavaOne 2008 Java hiệu quả hơn (pdf) "Cho hiệu suất cao trên trường tĩnh ..."
gnat

1

Các lớp riêng và bên trong được sử dụng để tăng mức độ đóng gói và ẩn chi tiết thực hiện.

Trong C ++, ngoài một lớp riêng, có thể đạt được khái niệm tương tự bằng cách triển khai không gian tên ẩn danh của lớp cpp. Điều này phục vụ độc đáo để ẩn / tư nhân hóa một chi tiết thực hiện.

Đó là cùng một ý tưởng như một lớp bên trong hoặc riêng tư nhưng ở mức độ đóng gói thậm chí còn lớn hơn vì nó hoàn toàn vô hình bên ngoài đơn vị biên dịch của tệp. Không có gì của nó xuất hiện trong tệp tiêu đề hoặc hiển thị bên ngoài trong khai báo của lớp.


Bất kỳ phản hồi mang tính xây dựng trên -1?
hiwaylon

1
Tôi đã không downvote nhưng tôi đoán rằng bạn không thực sự trả lời câu hỏi: bạn không đưa ra bất kỳ lý do nào để sử dụng lớp riêng tư / bên trong. Bạn chỉ mô tả cách thức hoạt động và cách thực hiện một điều tương tự trong c ++
Simon Bergot

Thật. Tôi nghĩ rằng đó là hiển nhiên của tôi và các câu trả lời khác (ẩn chi tiết thực hiện, tăng mức độ đóng gói, v.v.) nhưng tôi sẽ cố gắng làm rõ một số. Cảm ơn @Simon.
hiwaylon

1
Chỉnh sửa đẹp. Và xin đừng xúc phạm vì loại phản ứng này. Mạng SO đang cố gắng thực thi các chính sách để cải thiện tín hiệu tỷ lệ nhiễu. Một số người khá nghiêm ngặt về phạm vi câu hỏi / câu trả lời, và đôi khi quên mất việc trở nên tốt đẹp (ví dụ bằng cách bình luận downvote của họ)
Simon Bergot

@Simon Cảm ơn. Đó là thất vọng vì chất lượng truyền thông kém mà nó thể hiện. Có lẽ hơi quá dễ dàng để "nghiêm khắc" đằng sau bức màn ẩn danh của web? Ồ, không có gì mới tôi đoán. Cảm ơn một lần nữa cho các phản hồi tích cực.
hiwaylon
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.