2017-01-20 37 views
5

Postaram się wyjaśnić mój problem dotyczący samochodów. Mam AbstractCar, a użytkownicy (twórcy) mojej biblioteki utworzą wiele swoich ConcreteCars. Ten AbstractCar ma stan i ten stan jest bardzo ważny dla prawidłowego działania biblioteki! Tylko samochód może kontrolować swój stan (brak kierowców itp.). Zmiany stanu metod rozpoczynają/kończą się na początku i na końcu metod. Poza tym wszystkie samochody muszą implementować interfejs samochodowy.Java: Wzór do pracy ze stanem i dziedziczeniem

Próbowałem dwa warianty.

Wariant 1

public abstract class AbstractCar implements Car{ 
    private State state; 
    public void setState(State state){...} 
    public State getState(){...} 
} 

public class ConcreteCar extends AbstractCar{ 
    @Override 
    public void start(){ 
    setState(stateK); 
    ... 
    setState(stateN); 
    } 

    @Override 
    public void stop(){ 
    setState(stateR); 
    ... 
    setState(stateO); 
    } 
} 

W wariancie 1 użytkownik biblioteki będą musieli pamiętać, aby zmienić stan. Jeśli zapomni to zrobić, pojawi się błąd w kodzie.

Wariant 2

public abstract class AbstractCar implements Car{ 
    private State state; 
    protected void doOnStart(){ } 
    protected void doOnStop(){ } 
    public final void start(){ 
    state=...; 
    doOnStart(); 
    state=...; 
    } 
    public final void stop(){ 
    state=...; 
    doOnStop(); 
    state=...; 
    } 
} 

public class ConcreteCar extends AbstractCar{ 
    @Override 
    protected void doOnStart(){ 
    .... 
    } 

    @Override 
    protected void doOnStop(){ 
    ... 
    } 
} 

W wariancie 2 użytkownik nie może zapomnieć o stanie, ponieważ jest już poza jego kontrolą, ale jeśli mam wiele stanów i wiele metod, w których mogą być zmieniane w tym nie jest bardzo dobrym sposobem.

Czy ktoś mógłby doradzić jakikolwiek wzór lub technologie, jak rozwiązać ten problem?

+3

wariant 2 jest metoda wzorca Template: https://en.wikipedia.org/wiki/Template_method_pattern –

+0

@ cricket_007 właśnie przeczytałem o strukturze państwa, jak Rozumiem, że pozwala to samochodowi na zmianę zachowania w zależności od stanu. Ale nie potrzebuję innego zachowania. Muszę tylko kontrolować stan. –

+0

Pewnie. Z twojego psudeokodu, wydaje się, że masz coś w rodzaju uruchamiania, uruchamiania, zatrzymywania i zatrzymywania stanów? –

Odpowiedz

0

Użyj wzoru projektu stanu. https://sourcemaking.com/design_patterns/state

Zachowaj swój AbstractCar jako kontekst i wykorzystaj go do zarządzania stanem samochodu.

Wykonuję przykład w następujący sposób. Mam nadzieję, że rozumiem to poprawnie.

public interface IState { 
    public void changeState(Car inContext); 

    public void doSomething(); 
} 

public interface Car { 
    enum CarState{ 
     start, 
     stop, 
     auto 
    } 

    public void setState(CarState state); 
} 

public class AbstractCar implements Car { 
    IState m_currentState; 

    IState startState = new StartState(); 
    IState stopState = new StopState(); 
    IState autoState = new AutoNavigateState(); 

    public AbstractCar() { 
     m_currentState = stopState; 
    } 

    public void start() { 
     setState(CarState.start); 
     m_currentState.doSomething(); 
     m_currentState.changeState(this); 
    } 

    public void stop() { 
     setState(CarState.stop); 
     m_currentState.doSomething(); 
     m_currentState.changeState(this); 
    } 

    public void autoNavigate() { 
     setState(CarState.auto); 
     m_currentState.doSomething(); 
     m_currentState.changeState(this); 
    } 

    public void setState(CarState state) { 
     if (state == CarState.start) { 
      m_currentState = startState; 
     } else if (state == CarState.stop) { 
      m_currentState = stopState; 
     } else { 
      m_currentState = autoState; 
     } 
    } 

} 

public class StartState implements IState { 

    @Override 
    public void changeState(Car car) { 
     car.setState(CarState.stop); 
    } 

    @Override 
    public void doSomething() { 
     // TODO Auto-generated method stub 

    } 

} 

public class StopState implements IState{ 

    @Override 
    public void changeState(Car car) { 
     car.setState(CarState.start); 
    } 

    @Override 
    public void doSomething() { 
     // TODO Auto-generated method stub 

    } 

} 

public class AutoNavigateState implements IState{ 

    @Override 
    public void changeState(Car inContext) { 
     // TODO Auto-generated method stub 

    } 

    @Override 
    public void doSomething() { 
     // TODO Auto-generated method stub 

    } 

} 
0

W wariancie 2 użytkownik nie może zapomnieć o stanie, ponieważ jest już poza jego kontrolą

I jest pożądane?

public abstract class AbstractCar implements Car{ 
    ... 
    public final void start(){ 
    state=...; 
    doOnStart(); 
    state=...; 
    } 
    ... 
} 

W klasie abstrakcyjnej określa się dla konkretnych klas stan, w którym powinny one być używane.
Nie wydaje się bardzo elastycznym rozwiązaniem dla konkretnych klas, ponieważ zmiana stanu powinna zależeć od kontekstu, który może się zmienić w metodzie start().

należy użyć metody abstrakcyjne w AbstractCar zarówno umożliwiają i wymusić konkretne zajęcia do wyboru, jak określić ich stan, takie jak:

Załóżmy tę konkretną klasę:

public abstract class AbstractCar implements Car{ 
    ... 
    public abstract State getStateBeforeStart(); 
    public abstract State getStateAfterStart(); 
    ... 
    public final void start(){ 
    state = getStateBeforeStart(); 
    doOnStart(); 
    state = getStateAfterStart(); 
    } 
    ... 
} 

Można także użyć Javadoc poprawnie dokumentować API twoich zajęć i obowiązki konkretnych klas, aby sprzyjać dobrym używaniu go.

1

Jeśli chcesz zachować pełną kontrolę nad tym, w jakim stanie jest samochód w danym momencie i jakie przejścia są dozwolone, drugie podejście jest podstawowym wzorcem do użycia.

Możesz modyfikować sposób wywoływania kodu podklasy '(czy to przez wywołanie metody abstrakcyjnej, czy innego rodzaju wywołania zwrotnego), ale podstawowy wzór będzie taki sam - Twój kod AbstractCar będzie zawierał logikę stanów i przejść, z określonymi punktami, w których można nazwać "zewnętrzny" kod. Są one czasami określane jako "haki".

Przykładem takiego podejścia może być JSF life-cycle, gdzie żądanie przechodzi przez złożony proces roboczy, a w niektórych określonych fazach (np. Sprawdzanie poprawności) kod dostarczony przez użytkownika może zostać wykonany - ale nie ma możliwości bezpośrednio ustaw stan żądania.

Jeśli chcesz, aby Twoi użytkownicy (tj. Autorzy podklasy) mogli wpływać na stan samochodu, możesz to zrobić w kontrolowany sposób, akceptując wartość zwrotną z wywołania zwrotnego, która wpływa na następujące przejście stanu lub w niektórych przypadkach po prostu wykonując właściwej obsługi błędów:

public final void start(){ 
    state=STARTING; 
    try { 
     doOnStart(); 
     state=STARTED; 
    } catch (RuntimeException e) { 
     // handle error 
     state=STOPPED; 
    } 
}