Là bất biến rất đáng giá khi không có sự tương tranh?


53

Dường như an toàn luồng luôn / thường được đề cập là lợi ích chính của việc sử dụng các loại không thay đổi và đặc biệt là các bộ sưu tập.

Tôi có một tình huống mà tôi muốn đảm bảo rằng một phương thức sẽ không sửa đổi một từ điển các chuỗi (không thay đổi trong C #). Tôi muốn hạn chế mọi thứ càng nhiều càng tốt.

Tuy nhiên tôi không chắc liệu việc thêm một phụ thuộc vào gói mới (Bộ sưu tập bất biến của Microsoft) có đáng hay không. Hiệu suất cũng không phải là một vấn đề lớn.

Vì vậy, tôi đoán câu hỏi của tôi là liệu các bộ sưu tập bất biến được khuyến khích mạnh mẽ khi không có yêu cầu hiệu suất cứng và không có vấn đề an toàn luồng? Xem xét rằng ngữ nghĩa giá trị (như trong ví dụ của tôi) có thể hoặc không phải là một yêu cầu là tốt.


1
Sửa đổi đồng thời không có nghĩa là chủ đề. Chỉ cần nhìn vào tên được đặt khéo léo ConcurrentModificationExceptionthường được gây ra bởi cùng một luồng làm thay đổi bộ sưu tập trong cùng một luồng, trong phần thân của một foreachvòng lặp trên cùng một bộ sưu tập.

1
Ý tôi là bạn không sai, nhưng điều đó khác với những gì OP đang hỏi. Ngoại lệ đó được đưa ra bởi vì sửa đổi trong quá trình liệt kê là không được phép. Ví dụ, sử dụng một Đồng thời Từ điển vẫn có lỗi này.
edthethird

13
Có lẽ bạn cũng nên tự hỏi mình câu hỏi ngược lại: khi nào thì khả năng biến đổi đáng giá?
Giorgio

2
Trong Java, nếu tính biến đổi ảnh hưởng hashCode()hoặc equals(Object)thay đổi kết quả, nó có thể gây ra lỗi khi sử dụng Collections(ví dụ: trong một HashSetđối tượng được lưu trữ trong một "nhóm" và sau khi thay đổi, nó sẽ chuyển sang một cái khác).
SJuan76

2
@ DavorŽdralo Theo như sự trừu tượng của các ngôn ngữ cấp cao, tính bất biến lan tỏa khá khó khăn. Nó chỉ là một phần mở rộng tự nhiên của sự trừu tượng rất phổ biến (hiện tại ngay cả trong C) của việc tạo và âm thầm loại bỏ "các giá trị tạm thời". Có lẽ bạn muốn nói rằng đó là một cách không hiệu quả để sử dụng CPU, nhưng lập luận đó cũng có sai sót: Các ngôn ngữ năng động nhưng hạnh phúc thường hoạt động kém hơn các ngôn ngữ tĩnh nhưng chỉ bất biến, một phần vì có một số thông minh (nhưng cuối cùng khá đơn giản) thủ thuật để tối ưu hóa các chương trình tung hứng dữ liệu bất biến: Các loại tuyến tính, phá rừng, v.v.

Câu trả lời:


101

Tính không thay đổi đơn giản hóa lượng thông tin bạn cần theo dõi về mặt tinh thần khi đọc mã sau này . Đối với các biến có thể thay đổi và đặc biệt là các thành viên lớp có thể thay đổi, rất khó để biết họ sẽ ở trạng thái nào tại dòng cụ thể mà bạn đang đọc, mà không chạy qua mã với trình gỡ lỗi. Dữ liệu không thay đổi rất dễ để lý do - nó sẽ luôn giống nhau. Nếu bạn muốn thay đổi nó, bạn cần tạo ra một giá trị mới.

Tôi thực sự thích làm cho mọi thứ trở nên bất biến theo mặc định , và sau đó thay đổi chúng thành có thể thay đổi khi được chứng minh rằng chúng cần phải, cho dù điều này có nghĩa là bạn cần hiệu suất hay thuật toán mà bạn không có ý nghĩa cho sự bất biến.


23
+1 Đồng thời là đột biến đồng thời, nhưng đột biến lan truyền theo thời gian có thể khó lý giải như vậy
guillaume31

20
Để mở rộng về điều này: một hàm dựa vào một biến có thể thay đổi có thể được coi là lấy thêm một đối số ẩn, đó là giá trị hiện tại của biến có thể thay đổi. Bất kỳ hàm nào thay đổi một biến có thể thay đổi cũng có thể được coi là tạo ra một giá trị trả về bổ sung, tức là giá trị mới của trạng thái có thể thay đổi. Khi nhìn vào một đoạn mã bạn không biết nó có phụ thuộc hay thay đổi trạng thái có thể thay đổi hay không, do đó bạn phải tìm hiểu và sau đó theo dõi các thay đổi về mặt tinh thần. Điều này cũng giới thiệu khớp nối giữa bất kỳ hai đoạn mã nào có chung trạng thái có thể thay đổi và khớp nối là xấu.
Doval

29
@Mehrdad Mọi người cũng đã xoay sở để tạo ra các chương trình lớn trong lắp ráp trong nhiều thập kỷ. Sau đó, chúng tôi đã làm một vài thập kỷ của C.
Doval

11
@Mehrdad Sao chép toàn bộ đối tượng không phải là một lựa chọn tốt khi các đối tượng lớn. Tôi không thấy lý do tại sao thứ tự cường độ liên quan đến vấn đề cải tiến. Bạn có từ chối cải thiện 20% (lưu ý: số tùy ý) chỉ vì nó không phải là cải tiến ba chữ số? Bất biến là một mặc định lành mạnh ; bạn có thể đi lạc khỏi nó, nhưng bạn cần một lý do.
Doval

9
@Giorgio Scala khiến tôi nhận ra rằng bạn cần thường xuyên như thế nào để thậm chí tạo ra một giá trị có thể thay đổi. Bất cứ khi nào tôi sử dụng ngôn ngữ đó, tôi làm mọi thứ a val, và chỉ trong những dịp rất, rất hiếm khi tôi thấy rằng tôi cần thay đổi một cái gì đó thành a var. Rất nhiều 'biến' tôi xác định trong bất kỳ ngôn ngữ cụ thể nào chỉ ở đó để giữ một giá trị lưu trữ kết quả của một số tính toán và không cần phải cập nhật.
KChaloux

22

Mã của bạn nên thể hiện ý định của bạn. Nếu bạn không muốn một đối tượng được sửa đổi một khi được tạo, hãy làm cho nó không thể sửa đổi.

Bất biến có một số lợi ích:

  • Ý định của tác giả ban đầu được thể hiện tốt hơn.

    Làm thế nào bạn có thể biết rằng trong đoạn mã sau, sửa đổi tên sẽ khiến ứng dụng tạo ra một ngoại lệ ở đâu đó sau này?

    public class Product
    {
        public string Name { get; set; }
    
        ...
    }
    
  • Dễ dàng hơn để đảm bảo rằng đối tượng sẽ không xuất hiện ở trạng thái không hợp lệ.

    Bạn phải kiểm soát điều này trong một hàm tạo, và chỉ ở đó. Mặt khác, nếu bạn có một loạt các setters và phương thức sửa đổi đối tượng, các điều khiển như vậy có thể trở nên đặc biệt khó khăn, đặc biệt là, ví dụ, khi hai trường phải thay đổi cùng một lúc để đối tượng có hiệu lực.

    Ví dụ: một đối tượng hợp lệ nếu địa chỉ không null hoặc tọa độ GPS không null, nhưng không hợp lệ nếu cả địa chỉ và tọa độ GPS được chỉ định. Bạn có thể tưởng tượng địa ngục để xác nhận điều này nếu cả địa chỉ và tọa độ GPS đều có bộ cài đặt hoặc cả hai đều có thể thay đổi?

  • Đồng thời.

Nhân tiện, trong trường hợp của bạn, bạn không cần bất kỳ gói bên thứ ba nào. .NET Framework đã bao gồm một ReadOnlyDictionary<TKey, TValue>lớp.


1
+1, đặc biệt là "Bạn phải kiểm soát điều này trong một hàm tạo và chỉ ở đó." IMO đây là một lợi thế rất lớn.
Giorgio

10
Một lợi ích khác: sao chép một đối tượng là miễn phí. Chỉ là một con trỏ.
Robert Grant

1
@MainMa Cảm ơn bạn đã trả lời, nhưng theo tôi hiểu thì ReadOnlyDipedia không đưa ra bất kỳ đảm bảo nào rằng người khác sẽ không thay đổi từ điển cơ bản (ngay cả khi không đồng thời tôi có thể lưu tham chiếu vào từ điển gốc trong đối tượng để sử dụng sau). ReadOnlyDixi thậm chí được khai báo trong một không gian tên lạ: System.Collections.ObjectModel.
Den

2
@Den: Điều đó liên quan đến một trong những thú cưng của tôi: những người liên quan đến "chỉ đọc" và "bất biến" là từ đồng nghĩa. Nếu một đối tượng được gói gọn trong một trình bao bọc chỉ đọc và không có tham chiếu nào khác tồn tại hoặc được giữ lại ở bất kỳ đâu trong vũ trụ thì việc bọc đối tượng sẽ làm cho nó trở nên bất biến, và một tham chiếu đến trình bao bọc có thể được sử dụng như một cách viết tắt để đóng gói trạng thái của đối tượng chứa trong đó. Tuy nhiên, không có cơ chế nào mà mã có thể xác định liệu đó có phải là trường hợp không. Ngược lại, bởi vì wrapper ẩn kiểu của đối tượng bọc, gói một đối tượng bất biến ...
supercat

2
... sẽ khiến mã không thể biết liệu trình bao bọc kết quả có thể được coi là bất biến một cách an toàn hay không.
supercat

13

Có nhiều lý do đơn luồng để sử dụng tính bất biến. Ví dụ

Đối tượng A chứa đối tượng B.

Mã bên ngoài truy vấn đối tượng B của bạn và bạn trả lại nó.

Bây giờ bạn có ba tình huống có thể xảy ra:

  1. B là bất biến, không có vấn đề.
  2. B là có thể thay đổi, bạn tạo một bản sao phòng thủ và trả lại. Hiệu suất đạt nhưng không có rủi ro.
  3. B là đột biến, bạn trả lại nó.

Trong trường hợp thứ ba, mã người dùng có thể không nhận ra những gì bạn đã làm và có thể thay đổi đối tượng và bằng cách đó, thay đổi dữ liệu nội bộ của đối tượng mà bạn không kiểm soát được hoặc có thể nhìn thấy điều đó xảy ra.


9

Tính không thay đổi cũng có thể đơn giản hóa rất nhiều việc thực hiện các công cụ thu gom rác. Từ wiki của GHC :

[...] Tính bất biến của dữ liệu buộc chúng ta phải tạo ra nhiều dữ liệu tạm thời nhưng nó cũng giúp thu gom rác này nhanh chóng. Bí quyết là dữ liệu bất biến KHÔNG BAO GIỜ chỉ đến các giá trị trẻ hơn. Thật vậy, các giá trị trẻ chưa tồn tại vào thời điểm khi một giá trị cũ được tạo ra, vì vậy nó không thể được chỉ ra từ đầu. Và vì các giá trị không bao giờ được sửa đổi, nó cũng không thể được trỏ đến sau này. Đây là tài sản chính của dữ liệu bất biến.

Điều này giúp đơn giản hóa rất nhiều bộ sưu tập rác (GC). Bất cứ lúc nào chúng ta cũng có thể quét các giá trị cuối cùng được tạo và giải phóng những giá trị không được trỏ đến từ cùng một tập hợp (tất nhiên, gốc thực sự của hệ thống phân cấp giá trị trực tiếp nằm trong ngăn xếp). [...] Vì vậy, nó có hành vi phản trực giác: phần trăm lớn hơn các giá trị của bạn là rác - nó hoạt động càng nhanh. [...]


5

Mở rộng về những gì KChaloux tóm tắt rất tốt ...

Lý tưởng nhất là bạn có hai loại trường và do đó có hai loại mã sử dụng chúng. Cả hai trường đều không thay đổi và mã không phải tính đến khả năng biến đổi; hoặc các trường có thể thay đổi và chúng ta cần viết mã có ảnh chụp nhanh ( int x = p.x) hoặc xử lý các thay đổi đó một cách duyên dáng.

Theo kinh nghiệm của tôi, hầu hết các mã rơi vào giữa hai mã , là mã lạc quan : nó tự do tham chiếu dữ liệu có thể thay đổi, giả sử rằng cuộc gọi đầu tiên p.xsẽ có kết quả giống như cuộc gọi thứ hai. Và hầu hết thời gian, điều này là đúng, ngoại trừ khi hóa ra nó không còn nữa. Úi.

Vì vậy, thực sự, chuyển câu hỏi đó xung quanh: lý do của tôi để làm cho điều này có thể thay đổi là gì?

  • Giảm phân bổ bộ nhớ / miễn phí?
  • Tự nhiên có thể thay đổi? (ví dụ: quầy)
  • Tiết kiệm sửa đổi, tiếng ồn ngang? (const / trận chung kết)
  • Làm cho một số mã ngắn hơn / dễ dàng hơn? (init mặc định, có thể ghi đè sau)

Bạn có viết mã phòng thủ không? Bất biến sẽ giúp bạn tiết kiệm một số sao chép xung quanh. Bạn có viết mã lạc quan? Sự bất biến sẽ giúp bạn tránh khỏi sự điên rồ của con bọ kỳ lạ, không thể đó.


3

Một lợi ích khác của tính bất biến là đây là bước đầu tiên để làm tròn các vật thể bất biến này vào một hồ bơi. Sau đó, bạn có thể quản lý chúng để bạn không tạo nhiều đối tượng về mặt khái niệm và ngữ nghĩa đại diện cho cùng một thứ. Một ví dụ điển hình là Chuỗi của Java.

Đó là một hiện tượng nổi tiếng trong ngôn ngữ học rằng một vài từ xuất hiện rất nhiều, cũng có thể xuất hiện trong bối cảnh khác. Vì vậy, thay vì tạo nhiều Stringđối tượng, bạn có thể sử dụng một đối tượng bất biến. Nhưng sau đó, bạn cần phải giữ một người quản lý hồ bơi để chăm sóc những vật thể bất biến này.

Điều này sẽ giúp bạn tiết kiệm rất nhiều bộ nhớ. Đây cũng là một bài viết thú vị để đọc: http://en.wikipedia.org/wiki/Zipf%27s_law


1

Trong Java, C # và các ngôn ngữ tương tự khác, các trường loại lớp có thể được sử dụng để xác định các đối tượng hoặc để đóng gói các giá trị hoặc trạng thái trong các đối tượng đó, nhưng các ngôn ngữ không phân biệt giữa các cách sử dụng đó. Giả sử một đối tượng lớp Georgecó một trường loại char[] chars;. Trường đó có thể gói gọn một chuỗi ký tự theo một trong hai cách sau:

  1. Một mảng sẽ không bao giờ được sửa đổi, cũng không tiếp xúc với bất kỳ mã nào có thể sửa đổi nó, nhưng các tham chiếu bên ngoài có thể tồn tại.

  2. Một mảng mà không có tài liệu tham khảo bên ngoài tồn tại, nhưng George có thể sửa đổi tự do.

  3. Một mảng thuộc sở hữu của George, nhưng có thể tồn tại các khung nhìn bên ngoài, dự kiến ​​sẽ đại diện cho trạng thái hiện tại của George.

Ngoài ra, biến có thể, thay vì đóng gói một chuỗi ký tự, đóng gói chế độ xem trực tiếp thành chuỗi ký tự do một số đối tượng khác sở hữu

Nếu charshiện tại gói gọn chuỗi ký tự [gió] và George muốn charsgói gọn chuỗi ký tự [đũa phép], có một số điều George có thể làm:

A. Xây dựng một mảng mới chứa các ký tự [đũa] và thay đổi charsđể xác định mảng đó thay vì mảng cũ.

B. Xác định bằng cách nào đó một mảng ký tự có sẵn sẽ luôn giữ các ký tự [đũa] và thay đổi charsđể xác định mảng đó thay vì mảng cũ.

C. Thay đổi ký tự thứ hai của mảng được xác định bởi charsthành a.

Trong trường hợp 1, (A) và (B) là những cách an toàn để đạt được kết quả mong muốn. Trong trường hợp (2), (A) và (C) an toàn, nhưng (B) sẽ không [nó sẽ không gây ra vấn đề ngay lập tức, nhưng vì George sẽ cho rằng nó có quyền sở hữu mảng, nên sẽ cho rằng nó có thể thay đổi mảng theo ý muốn]. Trong trường hợp (3), các lựa chọn (A) và (B) sẽ phá vỡ mọi quan điểm bên ngoài và do đó, chỉ có lựa chọn (C) là chính xác. Vì vậy, biết cách sửa đổi chuỗi ký tự được đóng gói bởi trường đòi hỏi phải biết loại trường ngữ nghĩa đó là gì.

Nếu thay vì sử dụng một trường loại char[], đóng gói một chuỗi ký tự có khả năng thay đổi, mã đã sử dụng loại String, đóng gói một chuỗi ký tự không thay đổi, tất cả các vấn đề trên sẽ biến mất. Tất cả các trường loại Stringđóng gói một chuỗi các ký tự bằng cách sử dụng một đối tượng có thể chia sẻ sẽ không bao giờ thay đổi. Do đó, nếu một lĩnh vực loạiStringđóng gói "gió", cách duy nhất để làm cho nó gói gọn "đũa phép" là làm cho nó xác định được vật thể khác nhau - một vật chứa "đũa phép". Trong trường hợp mã giữ tham chiếu duy nhất đến đối tượng, việc biến đổi đối tượng có thể hiệu quả hơn so với việc tạo một đối tượng mới, nhưng bất cứ khi nào một lớp có thể thay đổi, cần phải phân biệt giữa các cách khác nhau để có thể gói gọn giá trị. Cá nhân tôi nghĩ rằng Ứng dụng Hungary nên được sử dụng cho việc này (tôi sẽ xem xét bốn cách sử dụng char[]là các loại khác biệt về mặt ngữ nghĩa, mặc dù hệ thống loại coi chúng giống hệt nhau - chính xác là loại tình huống mà Ứng dụng Hungary tỏa sáng), nhưng vì nó không phải là cách dễ nhất để tránh sự mơ hồ như vậy là thiết kế các loại bất biến chỉ gói gọn các giá trị một chiều.


Có vẻ như một câu trả lời hợp lý, nhưng hơi khó đọc và hiểu.
Den

1

Có một số ví dụ hay ở đây nhưng tôi muốn nhảy vào với một số cá nhân nơi sự bất biến đã giúp ích rất nhiều. Trong trường hợp của tôi, tôi bắt đầu thiết kế một cấu trúc dữ liệu đồng thời bất biến, chủ yếu với hy vọng có thể tự tin chạy mã song song với việc đọc và ghi chồng chéo và không phải lo lắng về điều kiện cuộc đua. Có một cuộc nói chuyện John Carmack đã truyền cảm hứng cho tôi để thực hiện nó khi anh ấy nói về một ý tưởng như vậy. Đây là một cấu trúc khá cơ bản và khá tầm thường để thực hiện như thế này:

nhập mô tả hình ảnh ở đây

Tất nhiên với một vài tiếng chuông và tiếng huýt sáo như nó có thể loại bỏ các yếu tố trong thời gian liên tục và để lại các lỗ có thể thu hồi được và có các khối bị hủy bỏ nếu chúng trở nên trống rỗng và có khả năng được giải phóng trong một trường hợp bất biến nhất định. Nhưng về cơ bản để sửa đổi cấu trúc, bạn sửa đổi phiên bản "tạm thời" và về cơ bản cam kết những thay đổi bạn đã thực hiện để có được một bản sao bất biến mới không chạm vào phiên bản cũ, với phiên bản mới chỉ tạo ra các bản sao mới của các khối mà phải được làm cho độc đáo trong khi sao chép nông và tham chiếu đếm những cái khác.

Tuy nhiên, tôi không tìm thấy nó hữu ích cho mục đích đa luồng. Rốt cuộc, vẫn còn vấn đề về khái niệm, trong đó, một hệ thống vật lý áp dụng đồng thời vật lý trong khi người chơi đang cố gắng di chuyển các yếu tố xung quanh trong một thế giới. Bạn đi cùng với bản sao bất biến nào của dữ liệu được chuyển đổi, dữ liệu mà người chơi đã chuyển đổi hoặc hệ thống vật lý đã chuyển đổi? Vì vậy, tôi đã không thực sự tìm thấy một giải pháp đơn giản và tốt đẹp cho vấn đề khái niệm cơ bản này ngoại trừ việc có các cấu trúc dữ liệu có thể thay đổi, chỉ khóa theo cách thông minh hơn và không khuyến khích việc đọc và ghi chồng lên các phần giống nhau của bộ đệm để tránh các luồng bị đình trệ. Đó là điều mà John Carmack dường như có thể tìm ra cách giải quyết trong các trò chơi của mình; ít nhất anh ta nói về nó như thể anh ta gần như có thể nhìn thấy một giải pháp mà không cần mở một chiếc xe giun. Tôi đã không đi xa như anh ấy về vấn đề đó. Tất cả những gì tôi có thể thấy là những câu hỏi thiết kế vô tận nếu tôi cố gắng chỉ song song mọi thứ xung quanh bất biến. Tôi ước tôi có thể dành một ngày để nhặt não của anh ấy vì hầu hết những nỗ lực của tôi bắt đầu với những ý tưởng mà anh ấy đã đưa ra.

Tuy nhiên, tôi tìm thấy giá trị to lớn của cấu trúc dữ liệu bất biến này trong các lĩnh vực khác. Tôi thậm chí còn sử dụng nó ngay bây giờ để lưu trữ những hình ảnh thực sự kỳ lạ và thực hiện truy cập ngẫu nhiên cần thêm một số hướng dẫn (dịch chuyển phải và một chút andcùng với một lớp chỉ dẫn con trỏ), nhưng tôi sẽ đề cập đến các lợi ích bên dưới.

Hệ thống hoàn tác

Một trong những nơi trực tiếp nhất mà tôi thấy được hưởng lợi từ việc này là hệ thống hoàn tác. Hoàn tác mã hệ thống từng là một trong những điều dễ bị lỗi nhất trong khu vực của tôi (ngành công nghiệp FX trực quan), và không chỉ trong các sản phẩm tôi làm việc mà cả các sản phẩm cạnh tranh (hệ thống hoàn tác của chúng cũng không ổn) vì có rất nhiều khác nhau các loại dữ liệu cần lo lắng về việc hoàn tác và làm lại đúng cách (hệ thống thuộc tính, thay đổi dữ liệu lưới, thay đổi shader không dựa trên thuộc tính như hoán đổi với nhau, thay đổi thứ bậc cảnh như thay đổi cha mẹ của trẻ, thay đổi hình ảnh / kết cấu, v.v ... v.v.).

Vì vậy, số lượng mã hoàn tác cần thiết là rất lớn, thường cạnh tranh với số lượng mã thực hiện hệ thống mà hệ thống hoàn tác phải ghi lại các thay đổi trạng thái. Bằng cách dựa vào cấu trúc dữ liệu này, tôi có thể đưa hệ thống hoàn tác xuống như sau:

on user operation:
    copy entire application state to undo entry
    perform operation

on undo/redo:
    swap application state with undo entry

Thông thường mã ở trên sẽ rất kém hiệu quả khi dữ liệu cảnh của bạn kéo dài hàng gigabyte để sao chép toàn bộ. Nhưng cấu trúc dữ liệu này chỉ sao chép những thứ không thay đổi và nó thực sự khiến nó đủ rẻ để lưu trữ một bản sao bất biến của toàn bộ trạng thái ứng dụng. Vì vậy, bây giờ tôi có thể thực hiện các hệ thống hoàn tác dễ dàng như mã ở trên và chỉ tập trung vào việc sử dụng cấu trúc dữ liệu bất biến này để sao chép các phần không thay đổi của trạng thái ứng dụng ngày càng rẻ hơn và rẻ hơn. Kể từ khi tôi bắt đầu sử dụng cấu trúc dữ liệu này, tất cả các dự án cá nhân của tôi đều có hệ thống hoàn tác chỉ sử dụng mẫu đơn giản này.

Bây giờ vẫn còn một số chi phí ở đây. Lần trước tôi đã đo được khoảng 10 kilobyte chỉ để sao chép toàn bộ trạng thái ứng dụng mà không thực hiện bất kỳ thay đổi nào đối với nó (điều này không phụ thuộc vào độ phức tạp của cảnh do cảnh được sắp xếp theo thứ bậc, vì vậy nếu không có gì bên dưới gốc thay đổi, chỉ có gốc được sao chép nông mà không cần phải xuống trẻ em). Đó là xa 0 byte cần thiết cho một hệ thống hoàn tác chỉ lưu trữ deltas. Nhưng với 10 kilobyte hoàn tác trên mỗi thao tác, đó vẫn chỉ là một megabyte trên 100 hoạt động của người dùng. Thêm vào đó, tôi vẫn có khả năng đè bẹp nó xuống trong tương lai nếu cần.

Ngoại lệ-An toàn

Ngoại lệ-an toàn với một ứng dụng phức tạp không phải là vấn đề nhỏ. Tuy nhiên, khi trạng thái ứng dụng của bạn là bất biến và bạn chỉ sử dụng các đối tượng nhất thời để thực hiện các giao dịch thay đổi nguyên tử, thì nó vốn đã an toàn ngoại lệ vì nếu bất kỳ phần nào của mã bị ném, thì tạm thời bị loại bỏ trước khi đưa ra một bản sao bất biến mới . Vì vậy, điều đó làm tầm thường hóa một trong những điều khó nhất mà tôi luôn tìm thấy để có được ngay trong một cơ sở mã C ++ phức tạp.

Quá nhiều người thường chỉ sử dụng các tài nguyên tuân thủ RAII trong C ++ và nghĩ rằng điều đó đủ để an toàn ngoại lệ. Thường thì không, vì một chức năng thường có thể gây ra tác dụng phụ cho các trạng thái vượt ra ngoài các trạng thái cục bộ trong phạm vi của nó. Nói chung, bạn cần bắt đầu xử lý các bộ bảo vệ phạm vi và logic rollback tinh vi trong những trường hợp đó. Cấu trúc dữ liệu này tạo ra nó vì vậy tôi thường không cần bận tâm đến điều đó vì các chức năng không gây ra tác dụng phụ. Họ đang trả lại các bản sao trạng thái ứng dụng bất biến thay vì chuyển đổi trạng thái của ứng dụng.

Chỉnh sửa không phá hủy

nhập mô tả hình ảnh ở đây

Chỉnh sửa không phá hủy về cơ bản là phân lớp / xếp chồng / kết nối các hoạt động với nhau mà không chạm vào dữ liệu gốc của người dùng (chỉ nhập dữ liệu đầu vào và dữ liệu đầu ra mà không chạm vào đầu vào). Việc triển khai thông thường với một ứng dụng hình ảnh đơn giản như Photoshop và có thể không được hưởng lợi nhiều từ cấu trúc dữ liệu này vì nhiều thao tác có thể chỉ muốn chuyển đổi từng pixel của toàn bộ hình ảnh.

Tuy nhiên, với chỉnh sửa lưới không phá hủy, chẳng hạn, rất nhiều thao tác thường chỉ muốn chuyển đổi một phần của lưới. Một hoạt động có thể chỉ muốn di chuyển một số đỉnh ở đây. Một người khác có thể chỉ muốn chia nhỏ một số đa giác ở đó. Ở đây, cấu trúc dữ liệu bất biến giúp một tấn tránh việc phải tạo toàn bộ bản sao của toàn bộ lưới chỉ để trả về một phiên bản mới của lưới với một phần nhỏ của nó đã thay đổi.

Giảm thiểu tác dụng phụ

Với các cấu trúc này trong tay, nó cũng giúp bạn dễ dàng viết các chức năng giảm thiểu tác dụng phụ mà không phải chịu một hình phạt hiệu suất lớn cho nó. Tôi đã thấy mình viết ngày càng nhiều chức năng chỉ trả lại toàn bộ cấu trúc dữ liệu bất biến theo giá trị trong những ngày này mà không phát sinh tác dụng phụ, ngay cả khi điều đó có vẻ hơi lãng phí.

Ví dụ, điển hình là sự cám dỗ để biến đổi một loạt các vị trí có thể là chấp nhận một ma trận và một danh sách các đối tượng và biến đổi chúng theo một kiểu có thể thay đổi. Những ngày này tôi thấy mình vừa trả lại một danh sách mới các đối tượng.

Khi bạn có nhiều chức năng như thế này trong hệ thống của mình mà không gây ra tác dụng phụ, điều đó chắc chắn giúp bạn dễ dàng suy luận về tính đúng đắn của nó cũng như kiểm tra tính chính xác của nó.

Lợi ích của bản sao giá rẻ

Vì vậy, dù sao đi nữa, đây là những lĩnh vực mà tôi thấy sử dụng nhiều nhất trong các cấu trúc dữ liệu bất biến (hoặc cấu trúc dữ liệu liên tục). Tôi cũng có một chút quá nhiệt tình ban đầu và tạo ra một cây bất biến và danh sách liên kết bất biến và bảng băm bất biến, nhưng theo thời gian, tôi hiếm khi tìm thấy nhiều sử dụng cho những thứ đó. Tôi chủ yếu tìm thấy việc sử dụng hầu hết các container giống như mảng bất biến chunky trong sơ đồ trên.

Tôi cũng vẫn còn rất nhiều mã làm việc với các biến đổi (tìm thấy nó là một điều cần thiết thực tế ít nhất là đối với mã cấp thấp), nhưng trạng thái ứng dụng chính là một hệ thống phân cấp bất biến, đi sâu từ một cảnh bất biến đến các thành phần bất biến bên trong nó. Một số thành phần rẻ hơn vẫn được sao chép đầy đủ, nhưng những thành phần đắt nhất như mắt lưới và hình ảnh sử dụng cấu trúc bất biến để cho phép những bản sao rẻ tiền một phần chỉ những phần cần chuyển đổi.


0

Có rất nhiều câu trả lời hay rồi. Đây chỉ là một thông tin bổ sung phần nào cụ thể cho .NET. Tôi đã tìm hiểu các bài đăng trên blog .NET cũ và tìm thấy một tổng hợp lợi ích tốt từ quan điểm của các nhà phát triển Bộ sưu tập bất biến của Microsoft:

  1. Ảnh chụp ngữ nghĩa, cho phép bạn chia sẻ các bộ sưu tập của mình theo cách mà người nhận có thể tin tưởng không bao giờ thay đổi.

  2. An toàn luồng ngầm định trong các ứng dụng đa luồng (không yêu cầu khóa để truy cập các bộ sưu tập).

  3. Bất cứ khi nào bạn có một thành viên lớp chấp nhận hoặc trả về một loại bộ sưu tập và bạn muốn bao gồm các ngữ nghĩa chỉ đọc trong hợp đồng.

  4. Chức năng lập trình thân thiện.

  5. Cho phép sửa đổi bộ sưu tập trong quá trình liệt kê, trong khi đảm bảo bộ sưu tập gốc không thay đổi.

  6. Họ triển khai các giao diện IReadOnly * tương tự mà mã của bạn đã xử lý để di chuyển rất dễ dàng.

Nếu ai đó trao cho bạn một ReadOnlyCollection, IReadOnlyList hoặc IE có thể đảm bảo duy nhất là bạn không thể thay đổi dữ liệu - không có gì đảm bảo rằng người trao cho bạn bộ sưu tập sẽ không thay đổi nó. Tuy nhiên, bạn thường cần một số niềm tin rằng nó sẽ không thay đổi. Các loại này không cung cấp các sự kiện để thông báo cho bạn khi nội dung của chúng thay đổi và nếu chúng thay đổi, điều đó có thể xảy ra trên một luồng khác, có thể trong khi bạn liệt kê nội dung của nó không? Hành vi như vậy sẽ dẫn đến hỏng dữ liệu và / hoặc ngoại lệ ngẫu nhiên trong ứng dụng của bạ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.