Tại sao chúng ta cần lớp bất biến?


83

Tôi không thể hiểu được các tình huống mà chúng ta cần một lớp bất biến là gì.
Bạn đã bao giờ phải đối mặt với bất kỳ yêu cầu nào như vậy chưa? hoặc bạn có thể vui lòng cho chúng tôi bất kỳ ví dụ thực tế nào mà chúng tôi nên sử dụng mẫu này không.


13
Tôi ngạc nhiên là không ai gọi cái này là một bản sao. Có vẻ như các bạn chỉ đang cố gắng kiếm điểm dễ dàng ... :) (1) stackoverflow.com/questions/1284727/mutable-or-immutable-class (2) stackoverflow.com/questions/3162665/immutable-class ( 3) stackoverflow.com/questions/752280/…
Javid Jamae

Câu trả lời:


82

Các câu trả lời khác dường như quá tập trung vào việc giải thích tại sao tính bất biến là tốt. Nó rất tốt và tôi sử dụng nó bất cứ khi nào có thể. Tuy nhiên, đó không phải là câu hỏi của bạn . Tôi sẽ xem xét từng điểm câu hỏi của bạn để cố gắng đảm bảo rằng bạn nhận được câu trả lời và ví dụ mà bạn cần.

Tôi không thể hiểu được các tình huống mà chúng ta cần một lớp bất biến là gì.

"Cần" ở đây là một thuật ngữ tương đối. Các lớp bất biến là một mẫu thiết kế, giống như bất kỳ mô hình / mẫu / công cụ nào, ở đó để làm cho phần mềm xây dựng dễ dàng hơn. Tương tự, rất nhiều mã đã được viết trước khi mô hình OO xuất hiện, nhưng hãy đếm tôi trong số các lập trình viên "cần" OO. Các lớp bất biến, như OO, không hoàn toàn cần thiết , nhưng tôi sẽ hành động như thể tôi cần chúng.

Bạn đã bao giờ phải đối mặt với bất kỳ yêu cầu nào như vậy chưa?

Nếu bạn không nhìn các đối tượng trong miền vấn đề với góc nhìn phù hợp, bạn có thể không thấy yêu cầu đối với một đối tượng bất biến. Có thể dễ dàng nghĩ rằng miền có vấn đề không yêu cầu bất kỳ lớp bất biến nào nếu bạn không biết khi nào sử dụng chúng một cách thuận lợi.

Tôi thường sử dụng các lớp không thay đổi trong đó tôi coi một đối tượng nhất định trong miền vấn đề của mình như một giá trị hoặc trường hợp cố định . Khái niệm này đôi khi phụ thuộc vào quan điểm hoặc góc nhìn, nhưng lý tưởng nhất, nó sẽ dễ dàng chuyển sang quan điểm đúng để xác định các đối tượng ứng viên tốt.

Bạn có thể hiểu rõ hơn về nơi mà các đối tượng bất biến thực sự hữu ích (nếu không thực sự cần thiết) bằng cách đảm bảo rằng bạn đã đọc nhiều sách / bài báo trực tuyến khác nhau để phát triển ý thức tốt về cách suy nghĩ về các lớp bất biến. Một bài viết hay để giúp bạn bắt đầu là lý thuyết và thực hành Java: Đột biến hay không đột biến?

Tôi sẽ cố gắng đưa ra một vài ví dụ dưới đây về cách người ta có thể nhìn thấy các đối tượng ở các góc độ khác nhau (có thể thay đổi và không thể thay đổi) để làm rõ ý tôi theo quan điểm.

... bạn có thể vui lòng cho chúng tôi bất kỳ ví dụ thực tế nào mà chúng tôi nên sử dụng mẫu này không.

Vì bạn đã yêu cầu các ví dụ thực tế, tôi sẽ cung cấp cho bạn một số, nhưng trước tiên, hãy bắt đầu với một số ví dụ cổ điển.

Đối tượng giá trị cổ điển

Chuỗi và số nguyên thường được coi là giá trị. Do đó, không ngạc nhiên khi thấy rằng lớp String và lớp Integer wrapper (cũng như các lớp wrapper khác) là bất biến trong Java. Một màu thường được coi là một giá trị, do đó, lớp Màu bất biến.

Counterexample

Ngược lại, một chiếc xe hơi thường không được coi là một vật có giá trị. Tạo mẫu xe hơi thường có nghĩa là tạo ra một lớp có trạng thái thay đổi (đồng hồ đo đường, tốc độ, mức nhiên liệu, v.v.). Tuy nhiên, có một số miền mà nó có thể là một đối tượng giá trị. Ví dụ: một chiếc ô tô (hoặc cụ thể là một mẫu ô tô) có thể được coi là một đối tượng giá trị trong một ứng dụng để tra cứu loại dầu động cơ phù hợp cho một loại xe nhất định.

Đang chơi bài

Bao giờ viết một chương trình chơi bài? Tôi đã làm. Tôi có thể đã đại diện cho một thẻ chơi như một vật thể có thể thay đổi với một bộ đồ và thứ hạng có thể thay đổi. Một ván bài poker rút thăm có thể là 5 trường hợp cố định trong đó việc thay thế lá bài thứ 5 trong tay tôi có nghĩa là biến đổi bài chơi thứ 5 thành một lá bài mới bằng cách thay đổi bộ đồ và xếp hạng của nó.

Tuy nhiên, tôi có xu hướng nghĩ về một thẻ chơi như một vật thể bất biến có một bộ đồ và thứ hạng cố định không thay đổi sau khi được tạo ra. Ván bài poker rút của tôi sẽ có 5 phiên bản và việc thay thế một lá bài trong tay tôi sẽ liên quan đến việc loại bỏ một trong những phiên bản đó và thêm một phiên bản ngẫu nhiên mới vào ván bài của tôi.

Phép chiếu bản đồ

Một ví dụ cuối cùng là khi tôi làm việc trên một số mã bản đồ nơi bản đồ có thể tự hiển thị trong các phép chiếu khác nhau . Mã ban đầu có bản đồ sử dụng một phiên bản phép chiếu cố định nhưng có thể thay đổi (giống như thẻ chơi có thể thay đổi ở trên). Thay đổi phép chiếu bản đồ có nghĩa là làm thay đổi các hình thức chiếu của bản đồ (kiểu chiếu, điểm trung tâm, thu phóng, v.v.).

Tuy nhiên, tôi cảm thấy thiết kế đơn giản hơn nếu tôi coi một phép chiếu như một giá trị bất biến hoặc trường hợp cố định. Thay đổi phép chiếu bản đồ có nghĩa là bản đồ tham chiếu đến một thể hiện phép chiếu khác chứ không phải thay đổi thể hiện phép chiếu cố định của bản đồ. Điều này cũng làm cho việc chụp các phép chiếu có tên như MERCATOR_WORLD_VIEW.


42

Các lớp bất biến nói chung đơn giản hơn nhiều để thiết kế, triển khai và sử dụng đúng cách . Một ví dụ là String: việc triển khai java.lang.Stringđơn giản hơn đáng kể so với std::stringtrong C ++, chủ yếu là do tính bất biến của nó.

Một lĩnh vực cụ thể mà tính không thay đổi tạo ra sự khác biệt đặc biệt lớn là tính đồng thời: các đối tượng bất biến có thể được chia sẻ một cách an toàn giữa nhiều luồng , trong khi các đối tượng có thể thay đổi phải được tạo ra an toàn cho luồng thông qua thiết kế và triển khai cẩn thận - thường thì điều này không phải là một nhiệm vụ tầm thường.

Cập nhật: Phiên bản Java thứ 2 hiệu quả giải quyết vấn đề này một cách chi tiết - xem Mục 15: Giảm thiểu khả năng thay đổi .

Xem thêm các bài viết liên quan này:


39

Java hiệu quả của Joshua Bloch nêu ra một số lý do để viết các lớp bất biến:

  • Tính đơn giản - mỗi lớp chỉ ở một trạng thái
  • An toàn chủ đề - vì không thể thay đổi trạng thái, không cần đồng bộ hóa
  • Viết theo một phong cách bất biến có thể dẫn đến mã mạnh hơn. Hãy tưởng tượng nếu Chuỗi không phải là bất biến; Bất kỳ phương thức getter nào trả về một Chuỗi sẽ yêu cầu việc triển khai để tạo một bản sao phòng thủ trước khi Chuỗi được trả lại - nếu không một ứng dụng khách có thể vô tình hoặc có ác ý phá vỡ trạng thái đó của đối tượng.

Nói chung, bạn nên làm cho một đối tượng không thể thay đổi được trừ khi có vấn đề nghiêm trọng về hiệu suất. Trong những trường hợp như vậy, các đối tượng trình tạo có thể thay đổi có thể được sử dụng để xây dựng các đối tượng bất biến, ví dụ: StringBuilder


1
Mỗi lớp ở một trạng thái hay mỗi Đối tượng?
Tushar Thakur

@TusharThakur, từng đối tượng.
joker

17

Hashmap là một ví dụ cổ điển. Điều bắt buộc là chìa khóa của bản đồ là bất biến. Nếu khóa không phải là bất biến và bạn thay đổi một giá trị trên khóa sao cho hashCode () sẽ dẫn đến một giá trị mới, thì bản đồ hiện đã bị hỏng (một khóa hiện nằm sai vị trí trong bảng băm.).


3
Tôi thà nói rằng bắt buộc không được thay đổi chìa khóa; Tuy nhiên, không có yêu cầu chính thức nào để nó là bất biến.
Péter Török

Không chắc bạn muốn nói gì về "chính thức".
Kirk Woll

1
Tôi nghĩ ý của anh ấy là, yêu cầu thực sự duy nhất là các khóa SHOULDN KHÔNG được thay đổi ... không phải là chúng KHÔNG THỂ thay đổi được (thông qua tính bất biến). Tất nhiên, một cách dễ dàng để ngăn các phím bị thay đổi là chỉ làm cho chúng bất biến ngay từ đầu! :-)
Platinum Azure

2
Ví dụ: download.oracle.com/javase/6/docs/api/java/util/Map.html : "Lưu ý: phải hết sức thận trọng nếu các đối tượng có thể thay đổi được sử dụng làm khóa bản đồ". Có nghĩa là, các đối tượng có thể thay đổi có thể được sử dụng làm khóa.
Péter Török

8

Java thực tế là một và tất cả các tham chiếu. Đôi khi một thể hiện được tham chiếu nhiều lần. Nếu bạn thay đổi một trường hợp như vậy, nó sẽ được phản ánh trong tất cả các tham chiếu của nó. Đôi khi bạn chỉ đơn giản là không muốn có điều này để cải thiện tính mạnh mẽ và an toàn của chuỗi. Sau đó, một lớp không thay đổi sẽ hữu ích để người ta buộc phải tạo một thể hiện mới và gán lại nó cho tham chiếu hiện tại. Bằng cách này, phiên bản gốc của các tham chiếu khác vẫn không bị ảnh hưởng.

Hãy tưởng tượng Java sẽ trông như thế nào nếu Stringcó thể thay đổi được.


12
Hoặc nếu DateCalendarcó thể thay đổi. Ồ, chờ đã, họ rồi, OH SH
gustafc 22/09/10

1
@gustafc: Lịch có thể thay đổi là được, nếu xét đến công việc của nó là tính toán ngày tháng (có thể được thực hiện bằng cách trả lại các bản sao, nhưng xem xét lịch nặng đến mức nào, thì tốt hơn theo cách này). nhưng Date - vâng, điều đó thật khó chịu.
Michael Borgwardt

Trên một số triển khai JRE Stringcó thể thay đổi! (gợi ý: một số phiên bản JRockit cũ hơn). Gọi string.trim () kết quả trong chuỗi ban đầu được tỉa
Salandur

6
@Salandur: Vậy thì nó không phải là "triển khai JRE". Đó là một triển khai của một cái gì đó giống như JRE, nhưng không phải.
Mark Peters

@Mark Peters: một tuyên bố rất đúng
Salandur

6

Chúng ta không cần các lớp bất biến, nhưng chắc chắn chúng có thể làm cho một số tác vụ lập trình dễ dàng hơn, đặc biệt khi có nhiều luồng tham gia. Bạn không phải thực hiện bất kỳ khóa nào để truy cập vào một đối tượng không thể thay đổi và mọi dữ kiện mà bạn đã thiết lập về một đối tượng như vậy sẽ tiếp tục đúng trong tương lai.


6

Hãy lấy một trường hợp cực đoan: hằng số nguyên. Nếu tôi viết một tuyên bố như "x = x + 1", tôi muốn bộc bạch 100% rằng số "1" bằng cách nào đó sẽ không trở thành 2, bất kể điều gì xảy ra ở bất kỳ nơi nào khác trong chương trình.

Bây giờ không sao, hằng số nguyên không phải là một lớp, nhưng khái niệm thì giống nhau. Giả sử tôi viết:

String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);

Trông đơn giản thôi. Nhưng nếu Chuỗi không phải là bất biến, thì tôi sẽ phải xem xét khả năng getCustomerName có thể thay đổi customerId, để khi tôi gọi getCustomerBalance, tôi sẽ nhận được số dư cho một khách hàng khác. Bây giờ bạn có thể nói, "Tại sao ai đó viết hàm getCustomerName lại khiến nó thay đổi id? Điều đó chẳng có nghĩa lý gì." Nhưng đó chính xác là nơi bạn có thể gặp rắc rối. Người viết đoạn mã trên có thể hiểu rằng các hàm sẽ không thay đổi tham số. Sau đó, ai đó đi cùng phải sửa đổi cách sử dụng khác của chức năng đó để xử lý trường hợp khách hàng có nhiều tài khoản dưới cùng một tên. Và anh ấy nói, "Ồ, đây là chức năng tên getCustomer tiện dụng này đã tìm kiếm tên. Tôi '

Tính bất biến đơn giản có nghĩa là một lớp đối tượng nào đó là hằng số, và chúng ta có thể coi chúng là hằng số.

(Tất nhiên người dùng có thể gán một "đối tượng hằng số" khác cho một biến. Ai đó có thể viết String s = "hello"; và sau đó viết s = "tạm biệt"; Trừ khi tôi tạo biến cuối cùng, tôi không thể chắc chắn rằng nó không bị thay đổi trong khối mã của riêng tôi. Giống như các hằng số nguyên đảm bảo với tôi rằng "1" luôn là một số giống nhau, nhưng không phải "x = 1" sẽ không bao giờ bị thay đổi bằng cách viết "x = 2". Nhưng tôi có thể tự tin rằng nếu tôi có một xử lý đối với một đối tượng bất biến, thì không hàm nào tôi chuyển nó vào có thể thay đổi nó đối với tôi, hoặc nếu tôi tạo hai bản sao của nó, thì thay đổi đối với biến giữ một bản sao sẽ không thay đổi khác. v.v.


5

Có nhiều lý do cho tính bất biến:

  • An toàn luồng: Các đối tượng bất biến không thể thay đổi cũng như không thể thay đổi trạng thái bên trong của nó, do đó không cần đồng bộ hóa nó.
  • Nó cũng đảm bảo rằng bất kỳ thứ gì tôi gửi qua (qua mạng) phải ở trạng thái giống như đã gửi trước đó. Nó có nghĩa là không ai (kẻ nghe trộm) có thể đến và thêm dữ liệu ngẫu nhiên vào tập hợp bất biến của tôi.
  • Nó cũng đơn giản hơn để phát triển. Bạn đảm bảo rằng không có lớp con nào tồn tại nếu một đối tượng là bất biến. Ví dụ: một Stringlớp học.

Vì vậy, nếu bạn muốn gửi dữ liệu thông qua một dịch vụ mạng và bạn muốn cảm giác đảm bảo rằng bạn sẽ có kết quả giống hệt như những gì bạn đã gửi, hãy đặt nó là bất biến.


Tôi không hiểu phần về việc gửi qua mạng, cũng như phần mà bạn nói rằng không thể mở rộng một lớp bất biến.
aioobe

@aioobe, gửi dữ liệu qua mạng, ví dụ như dịch vụ Web, RMI, vv Và cho mở rộng, bạn không thể mở rộng một lớp học bất biến String, hoặc một ImmutableSet, ImmutableList vv
Buhake Sindi

Không thể mở rộng String, ImmutableSet, ImmutableList, v.v. là một vấn đề hoàn toàn trực giao với tính bất biến. Không phải tất cả các lớp được gắn cờ finaltrong Java đều không thay đổi và không phải tất cả các lớp không thay đổi đều được gắn cờ final.
CHỈ LÀ Ý KIẾN CHÍNH XÁC CỦA TÔI

5

Tôi sẽ tấn công điều này từ một góc độ khác. Tôi thấy các đối tượng bất biến giúp cuộc sống của tôi dễ dàng hơn khi đọc mã.

Nếu tôi có một đối tượng có thể thay đổi, tôi không bao giờ chắc chắn giá trị của nó là gì nếu nó từng được sử dụng bên ngoài phạm vi trước mắt của tôi. Giả sử tôi tạo MyMutableObjectcác biến cục bộ của một phương thức, điền vào nó các giá trị, sau đó chuyển nó cho năm phương thức khác. BẤT KỲ MỘT trong những phương thức đó có thể thay đổi trạng thái của đối tượng của tôi, vì vậy một trong hai điều phải xảy ra:

  1. Tôi phải theo dõi phần thân của năm phương pháp bổ sung trong khi suy nghĩ về logic của mã của tôi.
  2. Tôi phải tạo năm bản sao phòng thủ lãng phí của đối tượng của mình để đảm bảo rằng các giá trị phù hợp được chuyển cho mỗi phương thức.

Điều đầu tiên làm cho việc lập luận về mã của tôi trở nên khó khăn. Điều thứ hai làm cho mã của tôi kém hiệu suất - về cơ bản tôi đang bắt chước một đối tượng bất biến với ngữ nghĩa copy-on-write, nhưng làm điều đó mọi lúc cho dù các phương thức được gọi có thực sự sửa đổi trạng thái đối tượng của tôi hay không.

Nếu tôi sử dụng thay vào đó MyImmutableObject, tôi có thể yên tâm rằng những gì tôi đặt chính là giá trị sẽ có trong vòng đời của phương pháp của tôi. Không có "hành động ma quái nào ở khoảng cách xa" sẽ thay đổi nó so với tôi và tôi không cần thiết phải tạo các bản sao phòng thủ của đối tượng của mình trước khi sử dụng năm phương pháp khác. Nếu các phương thức khác muốn thay đổi mọi thứ vì mục đích của chúng, chúng phải tạo bản sao - nhưng chúng chỉ làm điều này nếu chúng thực sự phải tạo một bản sao (trái ngược với việc tôi làm trước mỗi lần gọi phương thức bên ngoài). Tôi dành cho mình nguồn lực tinh thần để theo dõi các phương pháp thậm chí có thể không có trong tệp nguồn hiện tại của tôi và tôi dự phòng hệ thống không ngừng tạo ra các bản sao phòng thủ không cần thiết đề phòng.

(Nếu tôi ra ngoài thế giới Java và vào, chẳng hạn như thế giới C ++, trong số những thứ khác, tôi có thể gặp khó khăn hơn. Tôi có thể làm cho các đối tượng xuất hiện như thể chúng có thể thay đổi được, nhưng đằng sau hậu trường làm cho chúng sao chép một cách rõ ràng trên bất kỳ kiểu thay đổi trạng thái — đó là copy-on-write — không ai khôn ngoan hơn.)


Một lợi thế của các loại giá trị có thể thay đổi trong các ngôn ngữ hỗ trợ chúng và không cho phép tham chiếu liên tục đến chúng là bạn có được những lợi thế mà bạn mô tả (nếu một loại tham chiếu có thể thay đổi được chuyển qua tham chiếu đến một quy trình, quy trình đó có thể sửa đổi nó khi nó đang chạy , nhưng không thể làm cho các sửa đổi xảy ra sau khi nó quay trở lại) mà không có một số lỗi mà các đối tượng bất biến thường có.
supercat

5

2 xu của tôi cho những vị khách trong tương lai:


2 tình huống trong đó các đối tượng không thay đổi là lựa chọn tốt là:

Trong đa luồng

Các vấn đề đồng thời trong môi trường đa luồng rất có thể được giải quyết bằng cách đồng bộ hóa nhưng đồng bộ hóa là một vấn đề tốn kém (sẽ không tìm hiểu ở đây về "lý do"), vì vậy nếu bạn đang sử dụng các đối tượng không thay đổi thì không có đồng bộ hóa để giải quyết vấn đề đồng thời vì trạng thái Các đối tượng không thể thay đổi không thể thay đổi và nếu trạng thái không thể thay đổi thì tất cả các luồng có thể truy cập liền mạch đối tượng. Vì vậy, các đối tượng không thay đổi là một lựa chọn tuyệt vời cho các đối tượng được chia sẻ trong môi trường đa luồng.


Khóa dưới dạng cho các bộ sưu tập dựa trên băm

Một trong những điều quan trọng nhất cần lưu ý khi làm việc với bộ sưu tập dựa trên băm là khóa phải hashCode()luôn trả về cùng một giá trị cho thời gian tồn tại của đối tượng, bởi vì nếu giá trị đó bị thay đổi thì mục nhập cũ sẽ được thực hiện trong bộ sưu tập dựa trên băm sử dụng đối tượng đó không thể được truy xuất, do đó nó sẽ gây ra rò rỉ bộ nhớ. Vì trạng thái của các đối tượng không thể thay đổi không thể thay đổi nên chúng là một lựa chọn tuyệt vời làm chìa khóa trong bộ sưu tập dựa trên băm. Vì vậy, nếu bạn đang sử dụng đối tượng không thay đổi làm khóa cho bộ sưu tập dựa trên băm thì bạn có thể chắc chắn rằng sẽ không có bất kỳ rò rỉ bộ nhớ nào vì điều đó (tất nhiên vẫn có thể bị rò rỉ bộ nhớ khi đối tượng được sử dụng làm khóa không được tham chiếu từ bất kỳ đâu khác, nhưng đó không phải là vấn đề ở đây).


2

Các đối tượng bất biến là các thể hiện mà trạng thái của nó không thay đổi khi được khởi tạo. Việc sử dụng các đối tượng như vậy là yêu cầu cụ thể.

Lớp bất biến tốt cho mục đích lưu vào bộ nhớ đệm và nó an toàn cho luồng.


2

Nhờ tính bất biến, bạn có thể chắc chắn rằng hành vi / trạng thái của đối tượng bất biến bên dưới không thay đổi, nhờ đó bạn có thêm lợi thế khi thực hiện các thao tác bổ sung:

  • Bạn có thể sử dụng nhiều lõi / xử lý (xử lý đồng thời / song song ) một cách dễ dàng (vì trình tự hoạt động sẽ không còn quan trọng nữa.)

  • Có thể làm bộ nhớ đệm cho các hoạt động tốn kém (vì bạn chắc chắn về cùng một
    kết quả).

  • Có thể gỡ lỗi một cách dễ dàng (vì lịch sử chạy sẽ không còn là mối quan tâm
    nữa)


1

Sử dụng từ khóa cuối cùng không nhất thiết phải làm cho một cái gì đó bất biến:

public class Scratchpad {
    public static void main(String[] args) throws Exception {
        SomeData sd = new SomeData("foo");
        System.out.println(sd.data); //prints "foo"
        voodoo(sd, "data", "bar");
        System.out.println(sd.data); //prints "bar"
    }

    private static void voodoo(Object obj, String fieldName, Object value) throws Exception {
        Field f = SomeData.class.getDeclaredField("data");
        f.setAccessible(true);
        Field modifiers = Field.class.getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL);
        f.set(obj, "bar");
    }
}

class SomeData {
    final String data;
    SomeData(String data) {
        this.data = data;
    }
}

Chỉ là một ví dụ để chứng minh rằng từ khóa "cuối cùng" ở đó để ngăn lỗi của lập trình viên và không nhiều hơn thế. Trong khi việc chỉ định lại giá trị thiếu từ khóa cuối cùng có thể dễ dàng xảy ra một cách tình cờ, việc thay đổi giá trị đến độ dài này sẽ phải được thực hiện có chủ ý. Nó ở đó để cung cấp tài liệu và ngăn lỗi của lập trình viên.


Lưu ý rằng điều này không trực tiếp trả lời câu hỏi vì nó không phải là cuối cùng; đó là về các lớp bất biến. Bạn có thể có một lớp bất biến không có từ khóa cuối cùng bằng cách sử dụng các hạn chế truy cập thích hợp.
Mark Peters

1

Cấu trúc dữ liệu bất biến cũng có thể giúp ích khi viết mã các thuật toán đệ quy. Ví dụ: giả sử bạn đang cố gắng giải quyết vấn đề 3SAT . Một cách là làm như sau:

  • Chọn một biến chưa được gán.
  • Cung cấp cho nó giá trị của TRUE. Đơn giản hóa trường hợp bằng cách lấy ra các mệnh đề hiện đã được thỏa mãn và lặp lại để giải quyết trường hợp đơn giản hơn.
  • Nếu đệ quy trong trường hợp TRUE không thành công, thì hãy gán biến FALSE đó thay thế. Đơn giản hóa phiên bản mới này và lặp lại để giải quyết nó.

Nếu bạn có cấu trúc có thể thay đổi để đại diện cho vấn đề, thì khi bạn đơn giản hóa trường hợp trong nhánh TRUE, bạn sẽ phải:

  • Theo dõi tất cả các thay đổi bạn thực hiện và hoàn tác tất cả chúng khi bạn nhận ra vấn đề không thể giải quyết được. Điều này có chi phí lớn vì đệ quy của bạn có thể đi khá sâu và rất khó để viết mã.
  • Tạo một bản sao của phiên bản, sau đó sửa đổi bản sao. Điều này sẽ chậm vì nếu đệ quy của bạn sâu vài chục cấp, bạn sẽ phải tạo nhiều bản sao của cá thể.

Tuy nhiên, nếu bạn viết mã nó một cách thông minh, bạn có thể có một cấu trúc bất biến, trong đó bất kỳ thao tác nào đều trả về phiên bản cập nhật (nhưng vẫn không thay đổi) của vấn đề (tương tự như String.replace- nó không thay thế chuỗi, chỉ cung cấp cho bạn một phiên bản mới ). Cách đơn giản để thực hiện điều này là có cấu trúc "bất biến" chỉ cần sao chép và tạo một cấu trúc mới trên bất kỳ sửa đổi nào, giảm nó thành giải pháp thứ 2 khi có một cấu trúc có thể thay đổi, với tất cả chi phí đó, nhưng bạn có thể làm điều đó bằng nhiều cách khác cách hiệu quả.


1

Một trong những lý do giải thích cho "nhu cầu" đối với các lớp không thay đổi là sự kết hợp của việc truyền mọi thứ bằng tham chiếu và không hỗ trợ các chế độ xem chỉ đọc của một đối tượng (tức là của C ++ const).

Hãy xem xét trường hợp đơn giản của một lớp có hỗ trợ cho mẫu người quan sát:

class Person {
    public string getName() { ... }
    public void registerForNameChange(NameChangedObserver o) { ... }
}

Nếu stringkhông phải là bất biến, lớp sẽ không thể Persontriển khai registerForNameChange()chính xác, bởi vì ai đó có thể viết như sau, sửa đổi hiệu quả tên của người đó mà không kích hoạt bất kỳ thông báo nào.

void foo(Person p) {
    p.getName().prepend("Mr. ");
}

Trong C ++, getName()trả về a const std::string&có tác dụng trả về bằng tham chiếu và ngăn truy cập vào các trình đột biến, có nghĩa là các lớp bất biến không cần thiết trong ngữ cảnh đó.


1

Họ cũng cung cấp cho chúng tôi một sự đảm bảo. Đảm bảo tính bất biến có nghĩa là chúng ta có thể mở rộng chúng và tạo ra những người sáng chế mới để đạt được hiệu quả mà nếu không thì không thể.

http://en.wikipedia.org/wiki/Singleton_pattern


1

Một tính năng của các lớp bất biến vẫn chưa được gọi ra: lưu trữ một tham chiếu đến một đối tượng lớp không thay đổi sâu là một phương tiện hiệu quả để lưu trữ tất cả trạng thái có trong đó. Giả sử tôi có một đối tượng có thể thay đổi sử dụng một đối tượng không thể thay đổi sâu để chứa 50K thông tin trạng thái. Ngoài ra, giả sử rằng tôi muốn trong 25 lần tạo một "bản sao" của đối tượng ban đầu (có thể thay đổi) của tôi (ví dụ: đối với bộ đệm "hoàn tác"); trạng thái có thể thay đổi giữa các hoạt động sao chép, nhưng thường thì không. Tạo một "bản sao" của đối tượng có thể thay đổi sẽ chỉ yêu cầu sao chép một tham chiếu đến trạng thái bất biến của nó, vì vậy 20 bản sao chỉ đơn giản là 20 tham chiếu. Ngược lại, nếu trạng thái được giữ trong các đối tượng có thể thay đổi trị giá 50K, thì mỗi trong số 25 hoạt động sao chép sẽ phải tạo ra bản sao dữ liệu trị giá 50K của chính nó; giữ tất cả 25 bản sao sẽ yêu cầu giữ hơn một meg dữ liệu chủ yếu được sao chép. Mặc dù thao tác sao chép đầu tiên sẽ tạo ra một bản sao dữ liệu sẽ không bao giờ thay đổi và 24 thao tác khác về lý thuyết có thể chỉ đơn giản là tham chiếu lại điều đó, trong hầu hết các lần triển khai sẽ không có cách nào để đối tượng thứ hai yêu cầu bản sao của thông tin để biết rằng một bản sao bất biến đã tồn tại (*).

(*) Một mẫu đôi khi có thể hữu ích là các đối tượng có thể thay đổi phải có hai trường để giữ trạng thái của chúng - một ở dạng có thể thay đổi và một ở dạng bất biến. Các đối tượng có thể được sao chép dưới dạng có thể thay đổi hoặc bất biến và sẽ bắt đầu hoạt động với một hoặc tập tham chiếu khác. Ngay khi đối tượng muốn thay đổi trạng thái của nó, nó sẽ sao chép tham chiếu bất biến tới tham chiếu có thể thay đổi (nếu nó chưa được thực hiện) và vô hiệu hóa tham chiếu bất biến. Khi đối tượng được sao chép dưới dạng bất biến, nếu tham chiếu không thay đổi của nó không được đặt, một bản sao không thay đổi sẽ được tạo và tham chiếu không thay đổi được trỏ đến đó. Cách tiếp cận này sẽ yêu cầu một vài thao tác sao chép nhiều hơn so với "bản sao chính thức khi ghi" (ví dụ: yêu cầu sao chép một đối tượng đã bị đột biến vì bản sao cuối cùng sẽ yêu cầu thao tác sao chép,


1

Tại sao lớp Immutable?

Khi một đối tượng được khởi tạo thì trạng thái đó không thể thay đổi trong suốt thời gian tồn tại. Điều này cũng làm cho nó an toàn.

Ví dụ:

Rõ ràng là String, Integer và BigDecimal, v.v. Một khi những giá trị này được tạo ra thì không thể thay đổi trong suốt cuộc đời.

Ca sử dụng: Khi đối tượng kết nối Cơ sở dữ liệu được tạo với các giá trị cấu hình của nó, bạn có thể không cần thay đổi trạng thái của nó, nơi bạn có thể sử dụng một lớp không thay đổi


0

từ Java hiệu quả; Một lớp bất biến chỉ đơn giản là một lớp mà các thể hiện của nó không thể sửa đổi được. Tất cả thông tin có trong mỗi cá thể được cung cấp khi nó được tạo và được cố định trong suốt thời gian tồn tại của đối tượng. Các thư viện nền tảng Java chứa nhiều lớp bất biến, bao gồm String, các lớp nguyên thủy đóng hộp, BigInte- ger và BigDecimal. Có nhiều lý do chính đáng cho điều này: Các lớp bất biến dễ thiết kế, triển khai và sử dụng hơn các lớp có thể thay đổi. Chúng ít bị lỗi hơn và an toàn hơ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.