Wynikowa klasa jest jednak krucha — konstruktor dla dowolnej nowej wartości musi występować po wszystkich istniejących wartościach, dzięki czemu zapewniamy, że wszystkie serializowane wcześniej obiekty nie zmienią swoich wartości w trakcie deserializacji. Dzieje się tak, ponieważ postać serializowana stałych (temat 55.) opiera się jedynie na ich kolejnych numerach. Jeżeli stała, będąca składnikiem typu wyliczeniowego, zmieni swój numer kolejny, stała o tym numerze poddana serializacji zmieni swoją wartość podczas procesu deserializacji. Może istnieć jedna lub więcej operacji skojarzonych z każdą ze stałych, które są wykorzystywane jedynie wewnątrz pakietu, zawierającego klasę realizującą typ wyliczeniowy. Operacje takie najlepiej implementować jako metody prywatne w ramach pakietu. Każda stała typu wyliczeniowego zawiera ukrytą kolekcję operacji, pozwalających reagować odpowiednio dla odpowiednich stałych. Jeżeli klasa implementująca typ wyliczeniowy posiada metody, których działanie znacznie się różni w zależności od wartości stałej, powinieneś skorzystać z osobnych klas prywatnych lub anonimowych klas, zagnieżdżonych dla każdej ze stałych. Pozwala to każdej ze stałych posiadać własną implementację danej metody i automatycznie wywoływać odpowiednią implementację. Alternatywą jest tworzenie wielościeżkowych rozgałęzień, które wybierają odpowiednią metodę w zależności od stałej, na rzecz której wywołujemy tę metodę. Jest to rozwiązanie niechlujne, podatne na błędy i często wydajność tego rozwiązania jest niższa od rozwiązania, korzystającego z automatycznego wybie-rania metod przez maszynę wirtualną. 98 Efektywne programowanie w języku Java Obie techniki opisane w poprzednim akapicie są zilustrowane jeszcze jedną klasą, realizującą typ wyliczeniowy. Klasa ta, , reprezentuje działania wykonywane przez prosty kalkulator czterodziałaniowy. Poza pakietem, w którym zdefiniowana jest ta klasa, można skorzystać ze stałych klasy do wywołania metod klasy ( , ", itd.). Wewnątrz pakietu można dodatkowo wykonywać operacje matematyczne związane ze stałymi. Przypuszczalnie pakiet może eksportować obiekt kalkulatora udostępniający jedną lub więcej metod, które jako parametrów oczekują stałych klasy . Zwróć uwagę, że sama klasa jest klasą abstrak- cyjną, zawierającą jedną prywatną w ramach pakietu metodę abstrakcyjną — , która wykonuje odpowiednią operację matematyczną. Dla każdej stałej zdefiniowana jest wewnętrzna klasa anonimowa, więc każda stała może zdefiniować własną wersję metody . $ L ; # L ; # # # ; ; # 77N: JJV# # 9 U!=U: : ! ! ! L H; .L "Q" ! ! ! Q L 4,)H; .L "1" ! ! ! 1 L ',4%; .L "0" ! ! ! 0 L /,K,/%/-G .L "7" ! ! ! 7 Przedstawiony typ wyliczeniowy ma wydajność porównywalną do wydajności klasy korzystającej ze stałych wyliczeniowych typu . Dwa różne obiekty klasy reprezentującej typ wyliczeniowy nigdy nie reprezentują tej samej wartości, więc porówna-nie referencji, które jest bardzo szybkie, wystarczy do sprawdzenia równości logicznej. Klienci klasy mogą nawet użyć operatora ++ zamiast metody — wynik będzie identyczny, a operator ++ może nawet działać szybciej. Jeżeli klasa typu wyliczeniowego jest przydatna, może być klasą najwyższego poziomu — jeżeli jest związana z inną klasą najwyższego poziomu, powinna być statyczną klasą zagnieżdżoną tej klasy (temat 18.). Na przykład klasa , zawiera zbiór stałych wyliczeniowych typu , określających tryby zaokrąglania Rozdział 4. ♦ Odpowiedniki konstrukcji języka C 99 części dziesiętnych. Te tryby zaokrąglania stanowią użyteczny model abstrakcji, który nie jest zasadniczo przywiązany do klasy , — byłoby lepiej utworzyć osobną klasę "- ". Udostępnienie takiej klasy wszystkim programistom korzystającym z trybów zaokrągleń pozwoliłoby na zwiększenie spójności między różnymi API. Podstawowa implementacja wzorca bezpiecznego typu wyliczeniowego, zilustrowana za pomocą dwóch implementacji klasy , jest zamknięta. Użytkownicy nie mogą dodawać nowych elementów typu wyliczeniowego, ponieważ klasa nie udostępnia im konstruktora. Powoduje to, że klasa zachowuje się tak, jakby została zdefiniowana jako . Najczęściej jest to najlepsze rozwiązanie, ale istnieją przypadki, w których chcemy utworzyć rozszerzalną klasę typu wyliczeniowego. Może być to potrzebne na przykład do reprezentowania formatów kodowania rysunków, gdy chcesz, aby inni programiści mogli umożliwiać obsługę nowych formatów. Aby umożliwić rozszerzanie typu wyliczeniowego, wystarczy udostępnić zabezpie- czony konstruktor. Użytkownicy będą mogli dzięki temu dziedziczyć po istniejącej klasie, dodając w podklasach własne stałe. Nie musisz się obawiać, że stałe typu wyliczeniowego spowodują konflikt, tak jak było to w przypadku typu wyliczeniowego, korzystającego ze stałych . Rozszerzalny wariant wzorca bezpiecznego typu wyliczeniowego korzysta z przestrzeni nazw pakietu do tworzenia „magicznie admini-
|