Thực tiễn tốt nhất Trả lại đối tượng chỉ đọc


11

Tôi có câu hỏi "thực tiễn tốt nhất" về OOP trong C # (nhưng nó áp dụng cho tất cả các ngôn ngữ).

Xem xét việc có lớp thư viện với đối tượng được tiếp xúc với công chúng, thông qua người truy cập thuộc tính, nhưng chúng tôi không muốn công chúng (những người sử dụng lớp thư viện này) thay đổi nó.

class A
{
    // Note: List is just example, I am interested in objects in general.
    private List<string> _items = new List<string>() { "hello" }

    public List<string> Items
    {
        get
        {
            // Option A (not read-only), can be modified from outside:
            return _items;

            // Option B (sort-of read-only):
            return new List<string>( _items );

            // Option C, must change return type to ReadOnlyCollection<string>
            return new ReadOnlyCollection<string>( _items );
        }
    }
}

Rõ ràng cách tiếp cận tốt nhất là "tùy chọn C", nhưng rất ít đối tượng có biến thể ReadOnly (và chắc chắn không có lớp nào do người dùng định nghĩa có nó).

Nếu bạn là người dùng của lớp A, bạn có muốn thay đổi

List<string> someList = ( new A() ).Items;

tuyên truyền cho đối tượng gốc (A)? Hoặc có thể trả lại một bản sao miễn là nó được viết như vậy trong các bình luận / tài liệu không? Tôi nghĩ rằng phương pháp nhân bản này có thể dẫn đến các lỗi khá khó theo dõi.

Tôi nhớ rằng trong C ++, chúng ta có thể trả về các đối tượng const và bạn chỉ có thể gọi các phương thức được đánh dấu là const trên nó. Tôi đoán không có tính năng / mẫu như vậy trong C #? Nếu không tại sao họ không bao gồm nó? Tôi tin rằng nó được gọi là const đúng.

Nhưng một lần nữa, câu hỏi chính của tôi là về "những gì bạn mong đợi" hoặc cách xử lý Tùy chọn A vs B.


2
Hãy nhìn vào bài viết này về xem trước này của bộ sưu tập bất biến mới cho .NET 4.5: blogs.msdn.com/b/bclteam/archive/2012/12/18/... Bạn đã có thể nhận được chúng qua NuGet.
Kristof Claes

Câu trả lời:


10

Bên cạnh câu trả lời của @ Jesse, đó là giải pháp tốt nhất cho các bộ sưu tập: ý tưởng chung là thiết kế giao diện công cộng của bạn theo cách để nó chỉ trả về

  • các loại giá trị (như int, double, struct)

  • các đối tượng không thay đổi (như "chuỗi" hoặc các lớp do người dùng xác định chỉ cung cấp các phương thức chỉ đọc, giống như lớp A của bạn)

  • (chỉ khi bạn phải): các bản sao đối tượng, như "Tùy chọn B" của bạn thể hiện

IEnumerable<immutable_type_here> là bất biến của chính nó, vì vậy đây chỉ là một trường hợp đặc biệt ở trên.


19

Cũng lưu ý rằng bạn nên lập trình cho các giao diện, và nói chung, những gì bạn muốn trả về từ tài sản đó là một IEnumerable<string>. A List<string>là một chút không vì nó thường có thể thay đổi và tôi sẽ đi xa để nói rằng ReadOnlyCollection<string>với tư cách là người thực hiện ICollection<string>cảm giác như nó vi phạm Nguyên tắc thay thế Liskov.


6
+1, sử dụng an IEnumerable<string>là chính xác những gì người ta muốn ở đây.
Doc Brown

2
Trong .Net 4.5, bạn cũng có thể sử dụng IReadOnlyList<T>.
Svick

3
Lưu ý cho các bên quan tâm khác. Khi xem xét trình truy cập thuộc tính: nếu bạn chỉ trả về IEnumerable <chuỗi> thay vì Danh sách <chuỗi> bằng cách thực hiện return _list (+ truyền ẩn cho IEnumerable) thì danh sách này có thể được chuyển trở lại vào Danh sách <chuỗi> và thay đổi sẽ thay đổi tuyên truyền vào đối tượng chính. Bạn có thể trả về Danh sách mới <chuỗi> (_list) (+ truyền tiếp theo ẩn cho IEnumerable) sẽ bảo vệ danh sách nội bộ. Thông tin thêm về điều này có thể được tìm thấy ở đây: stackoverflow.com/questions
4056013 / từ

1
Có tốt hơn không khi yêu cầu bất kỳ người nhận nào của bộ sưu tập cần truy cập theo chỉ mục phải được chuẩn bị để sao chép dữ liệu vào một loại tạo điều kiện cho điều đó, hoặc tốt hơn là yêu cầu mã trả lại bộ sưu tập cung cấp dưới dạng có thể truy cập bằng chỉ mục? Trừ khi nhà cung cấp có khả năng có nó ở dạng không tạo điều kiện truy cập theo chỉ mục, tôi sẽ xem xét rằng giá trị giải phóng người nhận khỏi nhu cầu tạo bản sao dự phòng của họ sẽ vượt quá giá trị giải phóng nhà cung cấp khỏi phải cung cấp một hình thức truy cập ngẫu nhiên (ví dụ: chỉ đọc IList<T>)
supercat

2
Một điều tôi không thích về IEnumerable là bạn không bao giờ biết liệu nó có chạy như một coroutine hay nó chỉ là một đối tượng dữ liệu. Nếu nó chạy như một coroutine, nó có thể dẫn đến các lỗi tinh vi, vì vậy đôi khi điều này là tốt để biết. Đây là lý do tại sao tôi có xu hướng thích ReadOnlyCollection hoặc IReadOnlyList, v.v.
Steve Vermeulen

3

Tôi đã gặp một vấn đề tương tự một lúc trước và bên dưới là giải pháp của tôi, nhưng nó thực sự chỉ hoạt động nếu bạn cũng kiểm soát thư viện:


Bắt đầu với lớp học của bạn A

public class A
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

Sau đó lấy ra một giao diện từ đó chỉ với phần get của các thuộc tính (và bất kỳ phương thức đọc nào) và có A thực hiện điều đó

public interface IReadOnlyA
{
    public int N { get; }
}
public class A : IReadOnlyA
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

Sau đó, tất nhiên khi bạn muốn sử dụng phiên bản chỉ đọc, một diễn viên đơn giản là đủ. Đây thực sự chỉ là giải pháp C của bạn, nhưng tôi nghĩ tôi sẽ đưa ra phương pháp mà tôi đã đạt được


2
Vấn đề với điều này là, nó có thể đưa nó trở lại phiên bản có thể thay đổi. Và việc vi phạm LSP, trạng thái tạm dừng có thể quan sát được thông qua các phương thức của lớp cơ sở (trong trường hợp này) có thể được thay đổi bằng các phương thức mới từ lớp con. Nhưng nó ít nhất làm hài lòng ý định, ngay cả khi nó không thực thi nó.
user470365

1

Đối với bất cứ ai tìm thấy câu hỏi này và vẫn quan tâm đến câu trả lời - bây giờ có một tùy chọn khác đang được đưa ra ImmutableArray<T>. Đây là một vài điểm tại sao tôi nghĩ nó tốt hơn sau đó IEnumerable<T>hoặc ReadOnlyCollection<T>:

  • nó nói rõ rằng nó là bất biến (API biểu cảm)
  • nó nói rõ rằng bộ sưu tập đã được hình thành (nói cách khác, nó không được khởi tạo một cách lười biếng và bạn có thể lặp đi lặp lại nhiều lần)
  • nếu số lượng các mặt hàng được biết đến, nó không giấu kiến thức mà như IEnumerable<T>không
  • vì khách hàng không biết việc triển khai là gì IEnumerablehoặc IReadOnlyCollectanh ấy / cô ấy không thể cho rằng nó không bao giờ thay đổi (nói cách khác, không có gì đảm bảo rằng các mục của bộ sưu tập không bị thay đổi trong khi tham chiếu đến bộ sưu tập đó không thay đổi)

Ngoài ra nếu APi cần thông báo cho khách hàng về những thay đổi trong bộ sưu tập, tôi sẽ đưa ra một sự kiện với tư cách là một thành viên riêng biệt.

Lưu ý rằng tôi không nói rằng đó IEnumerable<T>là xấu nói chung. Tôi vẫn sẽ sử dụng nó khi chuyển bộ sưu tập làm đối số hoặc trả lại bộ sưu tập được khởi tạo một cách lười biếng (trong trường hợp cụ thể này có ý nghĩa để ẩn số lượng vật phẩm vì nó chưa được biết đến).

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.