Implementazione dello stato dell'oggetto in un linguaggio OO?

11

Mi è stato dato del codice Java da guardare, che simula una gara automobilistica, di cui include un'implementazione di una macchina di stato di base. Questa non è una classica macchina a stati informatici, ma semplicemente un oggetto che può avere più stati e può passare da uno stato all'altro in base a una serie di calcoli.

Per descrivere solo il problema, ho una classe Car, con una classe enum annidata che definisce alcune costanti per lo stato della macchina (come OFF, IDLE, DRIVE, REVERSE, ecc.). All'interno di questa stessa classe di auto, ho una funzione di aggiornamento, che consiste fondamentalmente in una dichiarazione di switch di grandi dimensioni che attiva lo stato corrente delle automobili, esegue alcuni calcoli e quindi modifica lo stato delle auto.

Per quanto posso vedere, lo stato di Cars viene utilizzato solo all'interno della sua classe.

La mia domanda è, è questo il modo migliore di affrontare l'implementazione di una macchina a stati della natura sopra descritta? Sembra la soluzione più ovvia, ma in passato ho sempre sentito dire che le "istruzioni switch sono pessime".

Il problema principale che posso vedere qui è che l'istruzione switch potrebbe diventare molto grande quando aggiungiamo altri stati (se ritenuto necessario) e il codice potrebbe diventare ingombrante e difficile da mantenere.

Quale sarebbe una soluzione migliore a questo problema?

    
posta PythonNewb 01.09.2016 - 03:20
fonte

7 risposte

13
  • Ho trasformato la macchina in una macchina a stati usando Pattern stato . Non si notano le istruzioni switch o if-then-else utilizzate per la selezione dello stato.

  • In questo caso, tutti gli stati sono classi interne ma potrebbero essere implementati in altro modo.

  • Ogni stato contiene gli stati validi su cui può essere modificato.

  • All'utente viene richiesto il prossimo stato nel caso ne sia possibile più di uno, o semplicemente per confermare nel caso ne sia possibile uno solo.

  • Puoi compilarlo ed eseguirlo per testarlo.

  • Ho usato una finestra di dialogo grafica perché era più facile in quel modo eseguirlo interattivamente in Eclipse.

IldiagrammaUMLètrattoda qui .

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JOptionPane;

public class Car {

    private State state;
    public static final int ST_OFF=0;
    public static final int ST_IDDLE=1;
    public static final int ST_DRIVE=2;
    public static final int ST_REVERSE=3;

    Map<Integer,State> states=new HashMap<Integer,State>();

    public Car(){
        this.states.put(Car.ST_OFF, new Off());
        this.states.put(Car.ST_IDDLE, new Idle());
        this.states.put(Car.ST_DRIVE, new Drive());
        this.states.put(Car.ST_REVERSE, new Reverse()); 
        this.state=this.states.get(Car.ST_OFF);
    }

    private abstract class State{

        protected List<Integer> nextStates = new ArrayList<Integer>();

        public abstract void handle();
        public abstract void change();

        protected State promptForState(String prompt){
            State s = state;
            String word = JOptionPane.showInputDialog(prompt);
            int ch = -1;
            try {
                ch = Integer.parseInt(word);
            }catch (NumberFormatException e) {
            }   

            if (this.nextStates.contains(ch)){
                s=states.get(ch);
            } else {
                System.out.println("Invalid option");
            }
            return s;               
        }       

    }

    private class Off extends State{

        public Off(){ 
            super.nextStates.add(Car.ST_IDDLE);             
        }

        public void handle() { System.out.println("Stopped");}

        public void change() {
            state = this.promptForState("Stopped, iddle="+Car.ST_IDDLE+": ");
        }

    }

    private class Idle extends State{
        private List<Integer> nextStates = new ArrayList<Integer>();
        public Idle(){
            super.nextStates.add(Car.ST_DRIVE);
            super.nextStates.add(Car.ST_REVERSE);
            super.nextStates.add(Car.ST_OFF);       
        }

        public void handle() {  System.out.println("Idling");}

        public void change() { 
            state=this.promptForState("Idling, enter 0=off 2=drive 3=reverse: ");
        }

    }

    private class Drive extends State{

        private List<Integer> nextStates = new ArrayList<Integer>();
        public Drive(){
            super.nextStates.add(Car.ST_IDDLE);
        }       
        public void handle() {System.out.println("Driving");}

        public void change() {
            state=this.promptForState("Idling, enter 1=iddle: ");
        }       
    }

    private class Reverse extends State{
        private List<Integer> nextStates = new ArrayList<Integer>();
        public Reverse(){ 
            super.nextStates.add(Car.ST_IDDLE);
        }           
        public void handle() {System.out.println("Reversing");} 

        public void change() {
            state = this.promptForState("Reversing, enter 1=iddle: ");
        }       
    }

    public void request(){
        this.state.handle();
    }

    public void changeState(){
        this.state.change();
    }

    public static void main (String args[]){
        Car c = new Car();
        c.request(); //car is stopped
        c.changeState();
        c.request(); // car is iddling
        c.changeState(); // prompts for next state
        c.request(); 
        c.changeState();
        c.request();    
        c.changeState();
        c.request();        
    }

}
    
risposta data 01.09.2016 - 05:58
fonte
16

switch statements are bad

È questo tipo di eccessiva semplificazione che conferisce alla programmazione orientata agli oggetti un brutto nome. L'utilizzo di if equivale a "cattivo" quando si utilizza un'istruzione switch. In ogni caso non stai spedendo polimorficamente.

Se devi avere una regola che si adatta a un morso del suono prova questo:

Switch statements become very bad the moment you have two copies of them.

Un'istruzione switch che non è duplicata in nessun'altra parte della base di codice può talvolta riuscire a non essere malvagia. Se i casi non sono pubblici, ma sono incapsulati, in realtà non sono affari di nessuno. Soprattutto se sai come e quando rifattarlo in classi. Solo perché puoi non significa che devi. È perché è possibile che sia meno importante farlo ora.

Se ti ritrovi a cercare di spingere sempre più cose nell'istruzione switch, diffondendo la conoscenza dei casi in giro o desiderando che non sia così malvagio limitarti a crearne una copia, è ora di ridefinire i casi in classi separate .

Se hai tempo per leggere più di alcuni suggerimenti sul refactoring delle istruzioni switch c2 ha una pagina molto ben bilanciata sulla dichiarazione switch odore .

Anche nel codice OOP, non tutti gli switch sono negativi. È come lo stai usando e perché.

    
risposta data 01.09.2016 - 07:04
fonte
2

L'auto è un tipo di macchina a stati. Le istruzioni switch sono il modo più semplice per implementare una macchina a stati priva di super stati e stati secondari.

    
risposta data 01.09.2016 - 04:01
fonte
2

Le istruzioni switch non sono male. Non ascoltare le persone che dicono cose come "cambiare le posizioni sono cattive"! Alcuni usi particolari delle istruzioni switch sono un antipattern, come l'uso di un interruttore per emulare la sottoclasse. (Ma puoi anche implementare questo antipattern con if's, quindi immagino che anche se siano cattivi!).

La tua implementazione suona bene. Sei corretto è difficile da mantenere se aggiungi molti altri stati. Ma questo non è solo un problema di implementazione: avere un oggetto con molti stati con un comportamento diverso è di per sé un problema. Immaginando la tua auto, 25 stati hanno mostrato comportamenti diversi e regole diverse per le transizioni di stato. Solo per specificare e documentare questo comportamento sarebbe un compito enorme. Avrai migliaia di regole di transizione di stato! La dimensione del switch sarebbe solo un sintomo di un problema più grande. Quindi, se possibile, evita di percorrere questa strada.

Un possibile rimedio è quello di rompere lo stato in sottostati indipendenti. Ad esempio, REVERSE è davvero uno stato distinto da DRIVE? Forse gli stati della macchina potrebbero essere suddivisi in due: Stato motore (OFF, IDLE, DRIVE) e direzione (FORWARD, REVERSE). Lo stato e la direzione del motore saranno probabilmente in gran parte indipendenti, quindi si riducono le regole di duplicazione logica e di transizione dello stato. Più oggetti con meno stati sono molto più facili da gestire di un singolo oggetto con numerosi stati.

    
risposta data 01.09.2016 - 08:17
fonte
1

Nel tuo esempio, le auto sono semplicemente macchine di stato nel senso classico del computer science. Hanno un insieme di stati piccoli e ben definiti e una sorta di logica di transizione dello stato.

Il mio primo suggerimento è quello di prendere in considerazione la scomposizione della logica di transizione nella sua funzione (o classe, se la tua lingua non supporta le funzioni di prima classe).

Il mio secondo suggerimento è di prendere in considerazione l'idea di scomporre la logica di transizione nello stato stesso, che avrebbe una sua funzione (o classe, se la tua lingua non supporta le funzioni di prima classe).

In entrambi gli schemi, il processo per lo stato di transizione sarebbe simile a questo:

mycar.transition()

o

mycar.state.transition()

Il secondo potrebbe, ovviamente, essere racchiuso nella classe dell'auto per assomigliare al primo.

In entrambi gli scenari, l'aggiunta di un nuovo stato (ad esempio, DRAFTING), comporterebbe solo l'aggiunta di un nuovo tipo di oggetto stato e la modifica degli oggetti che passano specificamente al nuovo stato.

    
risposta data 01.09.2016 - 05:37
fonte
0

Dipende da quanto può essere grande il switch .

Nel tuo esempio, penso che switch sia OK, poiché non ci sono altri stati in cui possa pensare che il tuo Car possa avere, quindi non aumenterebbe nel tempo.

Se l'unico problema è avere un grande switch in cui ogni case ha un sacco di istruzioni, quindi basta creare metodi privati distinti per ciascuno.

A volte le persone suggeriscono il modello di progettazione dello stato , ma è più appropriato quando si ha a che fare con la logica complessa e afferma di prendere decisioni aziendali diverse per molte operazioni distinte. Altrimenti, i problemi semplici dovrebbero avere soluzioni semplici.

In alcuni scenari, potresti avere metodi che eseguono solo attività quando lo stato è A o B, ma non C o D, o hanno più metodi con operazioni molto semplici che dipendono dallo stato. Quindi una o più dichiarazioni switch sarebbero migliori.

    
risposta data 01.09.2016 - 06:53
fonte
0

Sembra una macchina a stati della vecchia scuola del tipo che è stata usata prima che qualcuno facesse programmazione orientata agli oggetti, per non parlare di Design Patterns. Può essere implementato in qualsiasi linguaggio con istruzioni switch, come C.

Come altri hanno già detto, non c'è nulla di intrinsecamente sbagliato nelle istruzioni switch. Le alternative sono spesso più complicate e difficili da comprendere.

A meno che il numero di casi switch diventi ridicolmente grande, la cosa può rimanere abbastanza gestibile. Il primo passo per renderlo leggibile è sostituire il codice in ogni caso con una chiamata di funzione per implementare il comportamento dello stato.

    
risposta data 01.09.2016 - 14:21
fonte