2012-01-03 31 views
10

Trong cuốn sách Java Concurrency In Practice, nó giải thích những ưu điểm của các đối tượng "không thay đổi có hiệu quả" so với các đối tượng có thể thay đổi đồng thời. Nhưng nó không giải thích những lợi thế "đối tượng bất biến hiệu quả" sẽ cung cấp hơn thực sự các đối tượng bất biến.Các đối tượng không thay đổi có hiệu quả có hợp lý không?

Và tôi không hiểu: bạn không thể luôn luôn xây dựng một đối tượng thực sự không thay đổi tại thời điểm bạn quyết định xuất bản an toàn đối tượng "không thay đổi hiệu quả"? (thay vì làm "ấn phẩm an toàn" của bạn, bạn sẽ xây dựng một đối tượng thực sự không thay đổi và đó là nó)

Khi tôi thiết kế các lớp học, tôi không thể xây dựng một đối tượng thật bất biến. sử dụng đoàn đại biểu nếu cần thiết vv để xây dựng các đối tượng bọc khác, bản thân họ thực sự immmutable của khóa học) tại thời điểm này tôi quyết định "xuất bản một cách an toàn".

Vì vậy, đối tượng "không thay đổi có hiệu quả" và "ấn bản an toàn" của họ chỉ là trường hợp thiết kế kém hoặc API kém?

Bạn sẽ bị buộc phải sử dụng đối tượng không thay đổi hiệu quả và bị buộc phải xuất bản an toàn nơi bạn không thể xây dựng một đối tượng thực sự vượt trội hơn nhiều?

Trả lời

9

Có, chúng có ý nghĩa trong một số trường hợp. Một ví dụ đơn giản là khi bạn muốn một số thuộc tính được tạo ra một cách lười biếng và được lưu trữ để bạn có thể tránh được việc tạo ra nó nếu nó không bao giờ được truy cập. String là một ví dụ về một lớp không thay đổi hiệu quả thực hiện điều này (với mã băm của nó).

1

Sử dụng các đối tượng không thay đổi hiệu quả cho phép bạn tránh tạo số lượng đáng kể các lớp. Thay vì thực hiện các cặp lớp [mutable builder]/[immutable object], bạn có thể xây dựng một class không thay đổi hiệu quả. Tôi thường định nghĩa một giao diện bất biến và một lớp có thể thay đổi được thực hiện giao diện này. Một đối tượng được cấu hình thông qua các phương thức lớp có thể thay đổi, và sau đó được xuất bản thông qua giao diện bất biến của nó. Miễn là các khách hàng của chương trình thư viện của bạn vào giao diện, với họ các đối tượng của bạn vẫn không thay đổi được trong suốt thời gian được xuất bản của họ.

+0

Bất kỳ ví dụ nào về phương pháp này đều có sẵn? – user77115

3

Đối immutables tròn:

class Foo 
{ 
    final Object param; 
    final Foo other; 

    Foo(Object param, Foo other) 
    { 
     this.param = param; 
     this.other = other; 
    } 

    // create a pair of Foo's, A=this, B=other 
    Foo(Object paramA, Object paramB) 
    { 
     this.param = paramA; 
     this.other = new Foo(paramB, this); 
    } 

    Foo getOther(){ return other; } 
} 



// usage 
Foo fooA = new Foo(paramA, paramB); 
Foo fooB = fooA.getOther(); 
// publish fooA/fooB (unsafely) 

Một câu hỏi là, vì this của fooA đang bị rò rỉ bên trong constructor, là fooA vẫn còn là một chủ đề an toàn không thay đổi? Tức là, nếu một luồng khác đọc fooB.getOther().param, nó có được bảo đảm để xem paramA không? Câu trả lời là có, vì this không bị rò rỉ cho luồng khác trước khi hành động đóng băng đóng băng; chúng tôi có thể thiết lập các đơn đặt hàng hb/dc/mc theo yêu cầu để chứng minh rằng paramA là giá trị hiển thị duy nhất cho việc đọc.

Quay lại câu hỏi ban đầu của bạn. Trong thực tế luôn luôn có những hạn chế vượt ra ngoài những kỹ thuật thuần túy. Khởi tạo mọi thứ bên trong hàm tạo không nhất thiết là lựa chọn tốt nhất cho một thiết kế, xem xét tất cả các lý do kỹ thuật, hoạt động, chính trị và các lý do con người khác.

Bao giờ tự hỏi tại sao chúng ta được cho ăn để nghĩ rằng đó là một ý tưởng tối cao tuyệt vời?

Vấn đề sâu hơn là Java thiếu một mức giá rẻ chung chung cho xuất bản an toàn rẻ hơn so với dễ bay hơi. Java chỉ có nó cho các trường final; vì một lý do nào đó, hàng rào đó không có sẵn.

Bây giờ final mang hai ý nghĩa độc lập: thứ nhất, trường cuối cùng phải được chỉ định chính xác một lần; Thứ 2, ngữ nghĩa bộ nhớ của ấn phẩm an toàn. Hai ý nghĩa này không liên quan gì đến nhau. Nó khá khó hiểu để bó chúng lại với nhau. Khi mọi người cần ý nghĩa thứ hai, họ buộc phải chấp nhận ý nghĩa thứ nhất. Khi 1 là rất bất tiện để đạt được trong một thiết kế, mọi người tự hỏi những gì họ đã làm sai - không nhận ra rằng đó là Java đã làm sai.

Kết hợp hai ý nghĩa dưới một final làm cho nó tăng gấp đôi cộng thêm, vì vậy dường như chúng tôi có nhiều lý do và động lực để sử dụng final. Câu chuyện độc ác hơn thực sự là chúng ta buộc phải sử dụng nó bởi vì chúng ta không được lựa chọn linh hoạt hơn.

+0

+1 để có ví dụ và giải thích tuyệt vời. Ngoài ra tôi đã không đề cập đến "cuối cùng" nhưng đó thực sự là những gì chúng tôi phải sử dụng ... Về hai ý nghĩa: nó thậm chí còn khó hiểu hơn rằng * cuối cùng * cũng có thể được áp dụng cho các phương pháp nhưng tôi digress; Tôi thích ví dụ constructor mặc dù người ta cũng có thể làm điều đó bằng cách sử dụng một nhà máy và/hoặc một giao diện thông thạo. – NoozNooz42

0

Giả sử ta có một lớp học bất biến Foo với năm tài sản, tên Alpha, Beta, vv, và một mong muốn đem lại WithAlpha, WithBeta, vv phương pháp đó sẽ trở lại một thể hiện đó là giống hệt với bản gốc trừ trường hợp được đặc biệt tài sản đã thay đổi. Nếu lớp học thực sự và không thay đổi sâu sắc, các phương pháp phải có dạng:

 
Foo WithAlpha(string newAlpha) 
{ 
    return new Foo(newAlpha, Beta, Gamma, Delta, Epsilon); 
} 

Foo WithBeta(string newBeta) 
{ 
    return new Foo(Alpha, NewBeta, Gamma, Delta, Epsilon); 
} 

Ick. Vi phạm nghiêm trọng nguyên tắc "Không lặp lại chính mình" (DRY). Hơn nữa, việc thêm một thuộc tính mới vào lớp sẽ yêu cầu thêm nó vào từng phương thức duy nhất trong các phương thức đó.

Mặt khác, nếu mỗi Foo tổ chức một nội FooGuts trong đó bao gồm một constructor sao chép, một thay vì có thể làm một cái gì đó như:

 
Foo WithAlpha(string newAlpha) 
{ 
    FooGuts newGuts = new FooGuts(Guts); // Guts is a private or protected field 
    newGuts.Alpha = newAlpha; 
    return new Foo(newGuts); // Private or protected constructor 
} 

Số dòng mã cho mỗi phương pháp đã tăng lên, nhưng Lưu ý rằng trong khi Foo có thể không thay đổi nếu hàm tạo của nó được gọi là FooGuts mà bất kỳ tham chiếu bên ngoài nào tồn tại, hàm tạo của nó chỉ có thể truy cập được mã được tin cậy không duy trì bất kỳ tham chiếu nào sau khi xây dựng.

Các vấn đề liên quan