Passare attorno a token impl-specific in algoritmi generici in un sistema di tipo statico / strong

1

Ero interessato a risolvere diversi problemi non collegati usando tecniche di ricerca di grafi generici, quindi, dopo un po 'di giocherellando, ho sviluppato il seguente schema: ogni problema implementa un'interfaccia generica "Problema" che ha uno stato iniziale e diversi metodi che prendere in stati e mosse e restituire nuovi stati e mosse. Ad esempio, posso getMoves(state) , applyMove(state, move) o controllare isGoal(state) . Tutti i dettagli specifici del problema sono quindi in un unico punto e gli algoritmi generici possono passare attorno agli stati e spostarsi come "token" generici a cui non interessano realmente, utilizzando l'istanza Problema per valutare le loro proprietà. I problemi sono anche di conseguenza apolidi (o quale stato sono immutabili), il che rende il loro comportamento molto facile da ragionare.

Tutto andava bene in un sistema di tipo dinamico / anatra (JavaScript), ma nella migrazione dell'idea in un sistema di tipo statico / rigido (Java), ho riscontrato alcuni problemi.

Il problema più immediato era che questi "token" potevano davvero essere qualsiasi cosa. Array, oggetti, mappe, archi, qualunque cosa. Javascript non si è preoccupato, perché gli algoritmi li hanno semplicemente passati in giro come black-box, e i Problemi presumevano che gli avessero dato i propri token. In Java questo non è il caso. La prima soluzione più naturale è che tutti i "token" implementino un'interfaccia State o Move vuota. Questo ha già alcuni problemi, come se il mio stato fosse solo una struttura di dati, quindi ho bisogno di estendere o avvolgere la struttura, ma è fondamentalmente bene.

Il prossimo problema è che i token sono specifici per l'implementazione, quindi ogni volta che vengono dati al Problema, devono essere inseriti in ciò che realmente sono. Un sacco di casting mi colpisce come una bandiera rossa, quindi questo sembra un approccio cattivo (certamente spiacevole).

Per evitare tutto il casting, ho considerato brevemente qualcosa che coinvolgeva tonnellate di template (State < ChessProblem & gt ;, Move < ChessProblem >), ma sembrava un mal di testa che non avrebbe nemmeno risolto il problema.

Esiste un modo "corretto" / standard per realizzare questo in sistemi di tipo più severi? Questo è solo un cattivo schema da usare in loro (o un cattivo schema generale)? Ogni approfondimento è apprezzato. Inoltre, mentre io sono usando Java, se altre lingue statiche / severe hanno nuovi strumenti per questo genere di cose, mi piacerebbe sentirli.

    
posta Gankro 23.02.2014 - 19:00
fonte

1 risposta

1

Penso che tu sia quasi arrivato. Invece di avere il State s generico, cambierei le definizioni di Problem s. Presumo che tu stia attualmente pensando a qualcosa di simile:

interface State {
}

interface Problem {
    public State foo(State s);
}

class ConcreteState implements State {
    ...
}

class ConcreteProblem implements Problem {
    // cannot narrow the argument type due to LSP
    @Override
    public ConcreteState foo(State s) {
        ConcreteState state = (ConcreteState) s;
        ...
        return state;
    }
}

Ora rendiamo l'interfaccia Problem generica e otteniamo quanto segue:

interface State {
}

interface Problem<S extends State> {
    public S foo(S s);
}

class ConcreteState implements State {
    ...
}

class ConcreteProblem implements Problem<ConcreteState> {
    @Override
    public ConcreteState foo(ConcreteState s) {
        ...
    }
}

Per ottenere tutti i vantaggi, è necessario fare attenzione che State non lasci il Problem , ad es. memorizzandolo esternamente. Ciò significa che probabilmente Problem dovrebbe essere utilizzato anche come State -factory.

    
risposta data 23.02.2014 - 19:15
fonte

Leggi altre domande sui tag