2013-03-16 14 views
28

Mając coś takiego:Jak przedłużyć klasę enum z klasy abstrakcyjnej?

public enum Token 
{ 
    FOO("foo", "f"), 
    QUIT("quit", "q"), 
    UNKNOWN("", ""); 
    ... 

    public parse(String s) { 
     for (Token token : values()) { 
       ... 
       return token; 
     } 
     return UNKNOWN; 
    } 
} 

abstrakcyjna klasa:

abstract class Base 
{ 
    private boolean run; 

    Base() { 
     run = true; 

     while (run) { 
      inp = getInput(); 
      act(inp); 
     } 
    } 

    public boolean act(String s) { 
     boolean OK = true; 
     switch (Token.parse(inp)) { /* Enum */ 
      case FOO: 
        do_foo(); 
        break; 
      case QUIT: 
        run = false; 
        break; 
      case UNKNOWN: 
        print "Unknown" + inp; 
        OK = false; 
        break; 
      } 
     } 
     return OK; 
    } 
} 

i przedłużacz:

class Major extends Base 
{ 

} 

Co chcę jest rozszerzenie act jak na razie super nie obsługuje następnie próbuje obsłużyć go w Major. Na przykład. dodaj PRINT_STAT("print-statistics", "ps") - ale jednocześnie niech domyślny uchwyt klasy Base będzie domyślny jak QUIT.

Czy to całkowicie błędne podejście?

co zrobiłem do tej pory, to dodać interfejs Zazwyczaj:

public interface BaseFace 
{ 
     public boolean act_other(String inp); 
} 

aw klasie Base implements BaseFace:

 case UNKNOWN: 
      OK = act_other(inp); 

A w klasie Major:

public boolean act_other(String inp) { 
     if (inp.equals("blah")) { 
      do_blah(); 
      return true; 
     } 
     return false; 
    } 

to wygląda jak użyteczny projekt?

I główne pytanie:

Czy jest jakiś dobry sposób na rozszerzenie klasy Token tak, że można używać tego samego podejścia przełącznika w Major jak w Base? Zastanawiam się, czy istnieje jeden lepszy projekt, a drugi, jeśli muszę utworzyć nową klasę tokenów dla Major lub jeśli w jakiś sposób mogę rozszerzyć lub w inny sposób ponownie użyć istniejącego.


Edit: Punkt koncepcji jest mieć klasę Base że mogę łatwo ponownego wykorzystania w różnych projektach zajmujących się różnego rodzaju wejścia.

Odpowiedz

39

Wszystkich implikacji enume rozszerzyć Enum. W języku Java klasa może obejmować co najwyżej jeszcze jedną klasę.

Możesz jednak mieć klasę enum zaimplementować interfejs.

Od this Java tutorial on Enum Types:

Uwaga: Wszystkie teksty stałe w sposób dorozumiany przedłużyć java.lang.Enum. Ponieważ klasa może rozszerzyć tylko jednego rodzica (zobacz Deklarowanie klas), język Java nie obsługuje wielokrotnego dziedziczenia stanu (zobacz: Wielokrotne dziedziczenie stanu, implementacji i typu), a zatem wyliczenie nie może rozszerzyć niczego innego.

Edycja Java 8:

Jako Java 8, interfejs może zawierać default methods. Pozwala to na dołączanie implementacji metod (ale nie stanów) w interfejsach.Chociaż głównym celem tej możliwości jest umożliwienie ewolucji interfejsów publicznych, można jej użyć do dziedziczenia niestandardowej metody definiującej typowe zachowanie wśród wielu klas wyliczeniowych.

Może to być jednak łamliwe. Jeśli metoda o tej samej sygnaturze zostanie później dodana do klasy java.lang.Enum, zastąpi ona domyślne metody. (Gdy metoda jest zdefiniowana zarówno w nadrzędnej i interfejsy klasy, wykonania klasa zawsze wygrywa.)

Na przykład:

interface IFoo { 
    public default String name() { 
     return "foo"; 
    } 
} 

enum MyEnum implements IFoo { 
    A, B, C 
} 

System.out.println(MyEnum.A.name()); // Prints "A", not "foo" - superclass Enum wins 
+3

Przydatny link do samouczka: http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.9 – Aubin

+0

Dzięki. Czytałem to, ale oczywiście nie złapałem wszystkiego. Przyjrzę się interfejsowi. Podobnie jak twój awatar btw :). I dziękuję @Aubin, - za dodatkowy link. Nie czytałem jeszcze drobin, - ale wygląda na to, że powinienem.Wygląda na to, że jestem w języku, który lubię. – Zimzalabim

+0

W podobnej sytuacji, w której chciałem dziedziczyć nietrywialną implementację z abstrakcyjnej klasy bazowej, otrzymałem wyliczenie jako element instancji. –

2

Musisz czynnik poza interfejs. W końcu dość powszechną praktyką jest zawsze rozpoczynanie od interfejsu, a następnie dostarczanie abstrakcyjnej klasy do dostarczania domyślnych implementacji. Jeśli masz interfejs, możesz wyliczyć implementację interfejsu.

+0

Dzięki, będę nad tym pracował. Jestem raczej nowy w Javie, więc ten rodzaj danych jest bardzo pomocny. – Zimzalabim

3

Jak inni mówią tutaj, nie można przedłużyć wyliczenia. Z punktu widzenia projektu to rozwiązanie wygląda na zbyt ściśle powiązane. Radziłbym zastosować do tego bardziej dynamiczne podejście. Możesz utworzyć mapę zachowania:

Map<Token, Runnable> behaviors; 

Ta mapa może być łatwo zmodyfikowana lub zastąpiona. Możesz nawet przechowywać niektóre zestawy tych predefiniowanych zachowań. Na przykład:

behaviors.get(Token.parse(inp)).run(); 

(potrzebne jakieś dodatkowe kontrole są tutaj oczywiście)

I ostatnia uwaga: w większości przypadków uniknąć dziedziczenie

+0

Dziękuję. Tak, przyjrzałem się podejściu 'Map' zgodnie z [moim poprzednim Q] (http://stackoverflow.com/a/15440238/2148245). Wypróbowałem to, ale zgaduję, że coś przeoczyłem - nie, że nie chcę dalej zaglądać, ale zastanowiłem się nad drogą ... Może powinienem to zrobić teraz. – Zimzalabim

+0

Również - odnośnie * "ściśle powiązanego" * i * "unikaj dziedziczenia" *. Zrobiłem szybkie wyszukiwanie w Internecie na temat "java avoid spadkobierców" - i natknąłem się na te [Dlaczego unikać dziedziczenia Java "rozszerza"] (http://programmers.stackexchange.com/a/75203) i [Dlaczego rozszerza się zło] (http : //www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html), - ale muszę go przeczytać jeszcze parę razy, mam nadzieję, że zrozumiemy skalę problemu. – Zimzalabim

4

Twój problem wydaje się być dobrym kandydatem do Wzorca poleceń

Dobrą praktyką jest używanie wyliczenia jako logicznej grupy obsługiwanych działań. IMO, po pojedynczym wyliczeniu grupowania wszystkich obsługiwanych działań, poprawi czytelność twojego kodu. Mając to na uwadze, Reklamowe enum powinny zawierać wszystkie obsługiwane typy działań

enum Token 
{ 
    FOO("foo", "do_foo"), 
    QUIT("quit", "do_quit"), 
    PRINT_STATS("print", "do_print_stats"), 
    UNKNOWN("unknown", "unknown") 
    ..... 

} 

rozważyć utworzenie aktor interfejsu, który definiuje sposób powiedzieć czynu, jak pokazano poniżej:

public interface Actor 
{ 
    public void act(); 
} 

Zamiast pojedyncza klasa Base, która również może rzeczy, możesz mieć jedną klasę na obsługiwane polecenie dla np.

public class FooActor implements Actor 
{ 
    public void act() 
    { 
     do_foo(); //call some method like do_foo 
    } 
} 

public class PrintActor implements Actor 
{ 
    public void act() 
    { 
     print_stats(); //call some print stats 
    } 
} 

Wreszcie będzie kod sterownika, który odbędzie się na wejściu działania, które należy przeprowadzić, zainicjować odpowiednie aktor i wykonać akcję poprzez wywołanie metody akt().

public class Driver 
{ 
    public static void main(String[] args) 
    { 
     String command; // will hold the input string from the user. 

     //fetch input from the user and store it in command 

     Token token = Token.parse(command); 

     switch(token) 
     { 
     case FOO: 
        new FooActor().act(); 
        break; 

     case PRINT_STATS: 
        new PrintActor().act(); 
        break; 
      .... 

     } 


    } 
} 

Taki projekt zapewni łatwe dodawanie nowych poleceń, a kod pozostaje modułowy.

+0

Dobre punkty (chyba - byłem na Jawie tylko przez kilka tygodni). Mój problem z tym podejściem dla tego konkretnego przypadku polega na tym, że mam ** dużo ** * aktu * zwykle 50+. Dość nieliczne to jednolinijkowe. Oznaczałoby to 50+ plików i ponad 50 klas, czy mam rację? Albo mogę oczywiście dodać tylko klasę dla tych, którzy mają bardziej rozbudowaną bazę kodów. Kolejnym ważnym punktem jest ponowne użycie tej samej bazy ... – Zimzalabim

+0

Możesz dodać metodę aktu do wyliczenia, a zamiast przełącznika (tokena) wystarczy wywołać token.act(); – Dave

+0

Zestaw poleceń jest w trzech miejscach, które muszą być zsynchronizowane: enum, zaimplementowane klasy i "sterownik". Łatwo jest zrealizować polecenie w jednym miejscu i zapomnieć o innym. Prawdopodobnie oszczędziłbym sterownik i umieściłby instancje zaimplementowanych aktorów w innym parametrze konstruktora modułu wyliczającego. –

Powiązane problemy