2012-01-03 19 views
10

W książce Java Concurrency In Practice wyjaśnia zalety "skutecznie niezmiennych" obiektów w porównaniu z zmiennymi obiektami w sposób współbieżny. Nie wyjaśnia jednak, jaką przewagę nad "niezmiennymi" obiektami stanowiłyby niezmienne obiekty.Czy skutecznie niezmienne obiekty mają sens?

I nie rozumiem: nie możesz zawsze zbudować naprawdę niezmienny obiekt w chwili, gdy zdecydujesz się opublikować bezpiecznie "skutecznie niezmienny" obiekt? (zamiast robić "bezpieczną publikację" zbudowałeś naprawdę niezmienny obiekt i to wszystko)

Kiedy projektuję klasy, nie widzę przypadków, w których nie zawsze mógłbym zbudować obiekt naprawdę niezmienny (w razie potrzeby za pomocą delegacji itp. do budowania innych zapakowanych przedmiotów, oczywiście naprawdę niemożliwa do zaakceptowania) w momencie, gdy zdecyduję się "bezpiecznie opublikować".

Czy obiekt "skutecznie niemodyfikowalny" i ich "bezpieczna publikacja" jest tylko przypadkiem złego projektu lub słabych interfejsów API?

Gdzie byłbyś zmuszony do używania obiektu niezmiennie efektywnego i musiałbyś go bezpiecznie opublikować, skoro nie mógłbyś zbudować znacznie lepszego, naprawdę niezmiennego obiektu?

Odpowiedz

9

Tak, w niektórych przypadkach mają one sens. Łatwym przykładem jest wtedy, gdy chcesz, aby niektóre właściwości były generowane leniwie i buforowane, abyś mógł uniknąć narzutu generowania go, jeśli nie jest on nigdy dostępny. String jest przykładem niezmiennej klasy, która to robi (z hashcode).

1

Używanie efektywnych niezmiennych obiektów pozwala uniknąć tworzenia znacznej liczby klas. Zamiast tworzyć pary klas [mutable builder]/[immutable object], możesz zbudować jedną, efektywnie niezmienną klasę. Zwykle definiuję niezmienny interfejs i zmienną klasę, która implementuje ten interfejs. Obiekt jest konfigurowany za pomocą metod klasy, którą można modyfikować, a następnie publikowany za pośrednictwem niezmiennego interfejsu. Tak długo jak klienci twojego programu bibliotecznego do interfejsu, do nich twoje obiekty pozostają niezmienne przez ich opublikowane życie.

+0

Czy jest jakikolwiek przykład tego podejścia? – user77115

3

Dla okrągłych immutables:

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) 

pytanie jest, ponieważ this z fooA wyciekają wewnątrz konstruktora, jest fooA jeszcze wątek bezpieczny niezmienne? Oznacza to, że jeśli inny wątek czyta fooB.getOther().param, czy jest gwarantowany, aby zobaczyć paramA? Odpowiedź brzmi: tak, ponieważ this nie wyciekł do innego wątku przed zatrzymaniem akcji ; możemy ustanowić zamówienia hb/dc/mc wymagane przez specyfikację, aby udowodnić, że paramA jest jedyną widoczną wartością odczytu.

Powrót do pierwotnego pytania. W praktyce zawsze istnieją ograniczenia wykraczające poza czysto techniczne. Inicjalizacja wszystkiego wewnątrz konstruktora niekoniecznie jest najlepszą opcją dla projektu, biorąc pod uwagę wszystkie techniczne, operacyjne, polityczne i inne ludzkie przyczyny.

Zastanawiasz się, dlaczego jesteśmy karmieni myślą, że to świetny, najwyższy pomysł?

Im głębszy problem, Java brakuje ogólnego taniego fense do bezpiecznej publikacji, która jest tańsza niż lotny. Java ma go tylko dla pól final; z jakiegoś powodu ogrodzenie to nie jest dostępne w inny sposób.

Teraz final ma dwa niezależne znaczenia: 1., że ostatnie pole musi zostać przypisane dokładnie raz; 2. semantyka pamięci bezpiecznej publikacji. Te dwa znaczenia nie mają ze sobą nic wspólnego. Łączenie ich razem jest dość mylące. Kiedy ludzie potrzebują drugiego znaczenia, są również zmuszeni do zaakceptowania pierwszego znaczenia. Kiedy 1 jest bardzo niewygodny w projektowaniu, ludzie zastanawiają się, co zrobili źle - nie zdając sobie sprawy, że to JAVA źle zrobiła.

Łączenie dwóch znaczeń pod jednym numerem final powoduje, że jest on podwójny plus dobry, więc prawdopodobnie mamy więcej powodów i motywacji do korzystania z final. Im bardziej ponura historia jest w rzeczywistości, musimy ją wykorzystać, ponieważ nie mamy bardziej elastycznego wyboru.

+0

+1 za świetny przykład i wyjaśnienie. Nie wspomniałem również o "ostatecznym", ale jest to w istocie tym, czego musimy użyć ... Odnośnie dwóch znaczeń: jeszcze bardziej mylące jest to, że * ostateczny * może być również zastosowany do metod, ale dygresję;) Podoba mi się przykład konstruktora, chociaż można to również zrobić za pomocą fabryki i/lub płynnego interfejsu. – NoozNooz42

0

Załóżmy, że jeden ma niezmienną klasy Foo z pięciu właściwości nazwie Alpha, Beta, itd., A chce się zapewnić WithAlpha, WithBeta itp metod, które zwracają przypadek, który jest identyczny z oryginałem z wyjątkiem konkretnego właściwość zmieniona. Jeśli klasa jest prawdziwie i głęboko niezmienna, metody muszą przybrać postać:

 
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. Ogromne naruszenie zasad "nie powtarzaj się" (DRY). Ponadto dodanie nowej właściwości do klasy wymagałoby dodania jej do każdej z tych metod.

Z drugiej strony, jeśli każdy Foo przeprowadziła wewnętrzną FooGuts który zawierał konstruktor kopiujący, można zamiast tego zrobić coś takiego:

 
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 
} 

liczba linii kodu dla każdej metody wzrosła, ale metody nie muszą już odwoływać się do żadnych właściwości, których nie są "zainteresowane". Należy zauważyć, że chociaż Foo może nie być niezmienny, jeśli jego konstruktor został wywołany z FooGuts, do którego istnieje odwołanie zewnętrzne, jego konstruktor jest dostępny tylko dla kod, który jest zaufany, aby nie utrzymywać takiego odniesienia po zakończeniu budowy.

Powiązane problemy