Chciałbym mieć ograniczony stały katalog wystąpień pewnego skomplikowanego interfejsu. Standardowy wzór multiton ma kilka fajnych funkcji, takich jak leniwe tworzenie instancji. Jednak opiera się na kluczu takim jak String, który wydaje się być podatny na błędy i delikatny.Używanie enum do implementowania multitonów w Javie
Chciałbym wzór, który używa wyliczenia. Mają wiele wspaniałych funkcji i są solidne. Próbowałem znaleźć standardowy wzór projektu, ale narysowałem pustą. Więc wymyśliłem własne, ale nie jestem z tego powodu bardzo szczęśliwy.
Wzór używam jest następująco (interfejs jest bardzo uproszczony, żeby uczynić go czytelny):
interface Complex {
void method();
}
enum ComplexItem implements Complex {
ITEM1 {
protected Complex makeInstance() { return new Complex() { ... }
},
ITEM2 {
protected Complex makeInstance() { return new Complex() { ... }
};
private Complex instance = null;
private Complex getInstance() {
if (instance == null) {
instance = makeInstance();
}
return instance;
}
protected void makeInstance() {
}
void method {
getInstance().method();
}
}
Wzorzec ten ma kilka bardzo ciekawych funkcji do niego:
- z enum implementuje interfejs, który sprawia, że jego użycie jest całkiem naturalne: ComplexItem.ITEM1.method();
- Lazy inicjacja: jeśli konstrukcja jest kosztowna (mój przypadek użycia obejmuje czytanie plików), pojawia się tylko wtedy, gdy jest wymagana.
Powiedziawszy, że wydaje się to strasznie skomplikowane i "hackowate" dla tak prostego wymogu i nadpisuje metody enumowe w sposób, który nie jestem pewien, co projektanci języka zamierzali.
Ma również inną poważną wadę. W moim przypadku użycia chciałbym, aby interfejs rozszerzał Porównywalny. Niestety to zderza się z implementacją Enumable i sprawia, że kod jest nieskompilowany.
Jedną z rozważanych przeze mnie opcji było posiadanie standardowego wyliczenia, a następnie oddzielnej klasy, która odwzorowuje wyliczenie na implementację interfejsu (przy użyciu standardowego wzorca wielotonowy). To działa, ale enum nie realizuje już interfejsu, który wydaje mi się nie być naturalnym odzwierciedleniem intencji. Oddziela również implementację interfejsu od elementów wyliczeniowych, które wydają się słabą enkapsulacją.
Inną alternatywą jest to, że konstruktor wyliczeniowy implementuje interfejs (to znaczy we wzorze powyżej usuwa potrzebę metody "makeInstance"). Podczas działania tego usuwa zaletę polegającą na tym, że w razie potrzeby uruchamia się tylko konstruktorów). To również nie rozwiązuje problemu z rozszerzeniem Porównywalne.
Moje pytanie brzmi: czy ktoś może wymyślić bardziej elegancki sposób na zrobienie tego?
W odpowiedzi na komentarze spróbuję określić konkretny problem, który próbuję rozwiązać jako pierwszy, a następnie poprzez przykład.
- Istnieje stała zestaw obiektów, które implementują określonego interfejsu
- Obiekty są bezpaństwowcem: są one używane do hermetyzacji zachowanie ruchowe tylko
- Tylko zostaną wykorzystane podzbiór obiektów za każdym razem kod jest wykonany (w zależności od danych wejściowych użytkownika)
- Tworzenie tych obiektów jest drogie: trzeba to zrobić tylko raz i tylko w razie potrzeby
- Przedmiotem dzielić zachowanie partii
Można to zaimplementować z oddzielnymi klasami singleton dla każdego obiektu za pomocą oddzielnych klas lub klas nadrzędnych dla zachowania współużytkowanego. Wydaje się to niepotrzebnie skomplikowane.
Teraz przykład. System oblicza kilka różnych podatków w zbiorze regionów, z których każdy ma swój własny algorytm do kalkulacji podatków. Oczekuje się, że zbiór regionów nigdy się nie zmieni, ale regionalne algorytmy ulegną regularnej zmianie. Konkretne stawki regionalne muszą być ładowane w czasie wykonywania za pomocą usługi zdalnej, która jest wolna i droga. Za każdym razem, gdy system jest wywoływany, zostanie mu przydzielony inny zestaw regionów do obliczenia, więc powinien on tylko ładować stawki żądanych regionów.
Więc:
interface TaxCalculation {
float calculateSalesTax(SaleData data);
float calculateLandTax(LandData data);
....
}
enum TaxRegion implements TaxCalculation {
NORTH, NORTH_EAST, SOUTH, EAST, WEST, CENTRAL .... ;
private loadRegionalDataFromRemoteServer() { .... }
}
Literatura background: Mixing-in an Enum
myślę, że zgadzają się, że rozwiązaniem jest pokazać hackish i zbyt skomplikowane. Myślę, że powinieneś użyć najprostszej enum możliwej, a następnie po prostu użyć statycznej fabryki. Fabryka powinna buforować wartości i tworzyć nowe w razie potrzeby. – markspace
Leniwe ładowanie, czytanie z plików, ograniczony zestaw instancji ... Brzmi jak banda singletonów dla mnie. –
Oczekuje się, że emulatory będą niezmienne i można je przekształcać do postaci szeregowej, co może spowodować problemy. – wolfcastle