Làm thế nào để tôi xử lý setters trên các lĩnh vực bất biến?


25

Tôi có một lớp học với hai readonly intlĩnh vực. Chúng được phơi bày như là tài sản:

public class Thing
{
    private readonly int _foo, _bar;

    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public int Foo { get { return _foo; } set { } }

    public int Bar { get { return _bar; } set { } }
}

Tuy nhiên, điều đó có nghĩa là sau đây là mã hợp pháp hoàn hảo:

Thing iThoughtThisWasMutable = new Thing(1, 42);

iThoughtThisWasMutable.Foo = 99;  // <-- Poor, mistaken developer.
                                  //     He surely has bugs now. :-(

Các lỗi xuất phát từ giả định rằng sẽ hoạt động chắc chắn là ngấm ngầm. Chắc chắn, nhà phát triển nhầm lẫn nên đọc tài liệu. Nhưng điều đó không thay đổi thực tế rằng không có lỗi biên dịch hoặc thời gian chạy nào cảnh báo anh ta về vấn đề này.

Làm thế nào để Thingthay đổi lớp để các nhà phát triển ít có khả năng mắc lỗi ở trên?

Ném một ngoại lệ? Sử dụng một phương thức getter thay vì một tài sản?



1
Cảm ơn, @gnat. Bài đó (cả câu hỏi và câu trả lời) dường như được nói về giao diện, như trong Capital tôi Interfaces. Tôi không chắc đó là những gì tôi đang làm.
kdbanman

6
@gnat, đó là lời khuyên khủng khiếp. Các giao diện cung cấp một cách tuyệt vời để phục vụ các VO / DTO bất biến công khai mà vẫn dễ kiểm tra.
David Arno

4
@gnat, tôi đã làm và câu hỏi không có ý nghĩa gì vì OP dường như nghĩ rằng các giao diện sẽ phá hủy tính bất biến, điều này là vô nghĩa.
David Arno

1
@gnat, câu hỏi đó là về việc khai báo giao diện cho DTOs. Câu trả lời được bình chọn hàng đầu là các giao diện sẽ không gây hại, nhưng có lẽ không cần thiết.
Paul Draper

Câu trả lời:


107

Tại sao làm cho mã đó hợp pháp?
Lấy ra set { }nếu nó không làm gì.
Đây là cách bạn xác định một tài sản công cộng chỉ đọc:

public int Foo { get { return _foo; } }

3
Tôi đã không nghĩ rằng đó là hợp pháp vì một số lý do, nhưng nó dường như hoạt động hoàn hảo! Hoàn hảo, cảm ơn bạn.
kdbanman

1
@kdbanman Có lẽ nó có liên quan đến thứ mà bạn đã thấy IDE đang làm tại một thời điểm - có thể là tự động hoàn thành.
Panzercrisis

2
@kdbanman; những gì không hợp pháp (tối đa C # 5) là public int Foo { get; }(tài sản tự động chỉ với một getter) Nhưng public int Foo { get; private set; }là.
Laurent LA RIZZA

@LaurentLARIZZA, bạn đã chạy bộ nhớ của tôi. Đó chính xác là nơi mà sự nhầm lẫn của tôi đến từ.
kdbanman

45

Với C # 5 trở về trước, chúng tôi đã phải đối mặt với hai tùy chọn cho các trường không thay đổi được hiển thị thông qua một getter:

  1. Tạo một biến sao lưu chỉ đọc và trả về biến đó thông qua một getter thủ công. Tùy chọn này là an toàn (người ta phải loại bỏ rõ ràng readonlyđể phá hủy tính bất biến. Mặc dù vậy, nó đã tạo ra rất nhiều mã nồi hơi.
  2. Sử dụng một thuộc tính tự động, với một setter riêng. Điều này tạo ra mã đơn giản hơn, nhưng dễ dàng hơn để vô tình phá vỡ tính bất biến.

Mặc dù với C # 6 (có sẵn trong VS2015, được phát hành ngày hôm qua), giờ đây chúng ta đã có được thứ tốt nhất của cả hai thế giới: thuộc tính tự động chỉ đọc. Điều này cho phép chúng tôi đơn giản hóa lớp OP để:

public class Thing
{
    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; }
    public int Bar { get; }
}

Các dòng Foo = fooBar = barchỉ hợp lệ trong hàm tạo (đạt được yêu cầu chỉ đọc mạnh mẽ) và trường sao lưu được ngụ ý, thay vì phải được xác định rõ ràng (đạt được mã đơn giản hơn).


2
Và các nhà xây dựng chính có thể đơn giản hóa điều này hơn nữa, bằng cách loại bỏ chi phí cú pháp của nhà xây dựng.
Sebastian Redl


1
@DanLyons Chết tiệt, tôi đã mong chờ được sử dụng cái này trong suốt cơ sở mã của chúng tôi.
Sebastian Redl

12

Bạn chỉ có thể thoát khỏi các setters. Họ không làm gì cả, họ sẽ gây nhầm lẫn cho người dùng và họ sẽ dẫn đến lỗi. Tuy nhiên, thay vào đó, bạn có thể đặt chúng ở chế độ riêng tư và do đó loại bỏ các biến sao lưu, đơn giản hóa mã của bạn:

public class Thing
{
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; private set; }

    public int Bar { get; private set; }
}

1
" public int Foo { get; private set; }" Lớp đó không còn bất biến vì nó có thể tự thay đổi. Cảm ơn, mặc dù!
kdbanman

4
Lớp được mô tả là bất biến, vì nó không tự thay đổi.
David Arno

1
Lớp học thực tế của tôi phức tạp hơn một chút mà MWE tôi đã đưa ra. Tôi sau khi bất biến thực sự , hoàn thành với sự an toàn của luồng và đảm bảo nội bộ lớp.
kdbanman

4
@kdbanman, và quan điểm của bạn là gì? Nếu lớp của bạn chỉ ghi vào các setters riêng trong khi xây dựng, thì nó đã thực hiện "bất biến thực sự". Các readonlytừ khóa chỉ là một Poka-Yoke, tức là nó bảo vệ chống lại giai cấp phá vỡ immutablity trong tương lai; nó không cần thiết để đạt được sự bất biến.
David Arno

3
Thuật ngữ thú vị, tôi đã phải google nó - poka-yoke là một thuật ngữ tiếng Nhật có nghĩa là "chống lỗi". Bạn đúng! Đó chính xác là những gì tôi đang làm.
kdbanman

6

Hai giải pháp.

Đơn giản:

Đừng bao gồm các setters như David lưu ý cho các đối tượng chỉ đọc bất biến.

Cách khác:

Cho phép setters trả về một đối tượng bất biến mới, dài dòng so với trước đây nhưng cung cấp trạng thái theo thời gian cho từng đối tượng được khởi tạo. Thiết kế này là một công cụ rất hữu ích cho sự an toàn và bất biến của Thread, trải rộng trên tất cả các ngôn ngữ OOP bắt buộc.

Mã giả

public class Thing
{

{readonly vars}

    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}

nhà máy tĩnh công cộng

public class Thing
{

{readonly vars}

    private Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public static with(int foo, int bar) {
        return new Thing(foo, bar)
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}

2
setXYZ là những cái tên khủng khiếp cho các phương thức xuất xưởng, hãy xem xét vớiXYZ hoặc tương tự.
Ben Voigt

Cảm ơn, cập nhật câu trả lời của tôi để phản ánh bình luận hữu ích của bạn. :-)
Matt Smithies

Câu hỏi được hỏi cụ thể về setters tài sản , không phải phương thức setter.
dùng253751

Đúng, nhưng OP lo lắng về trạng thái đột biến từ một nhà phát triển có thể trong tương lai. Các biến sử dụng từ khóa riêng chỉ đọc hoặc riêng tư cuối cùng sẽ tự ghi lại, đó không phải là lỗi của nhà phát triển ban đầu nếu điều đó không được hiểu. Hiểu các phương pháp khác nhau để thay đổi trạng thái là chìa khóa. Tôi đã thêm một cách tiếp cận khác rất hữu ích. Có rất nhiều thứ trong thế giới mã gây ra sự hoang tưởng quá mức, các lọ chỉ đọc riêng không nên là một trong số chúng.
Matt Smithies
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.