Due interfacce con firme identiche

13

Sto tentando di modellare un gioco di carte in cui le carte hanno due serie importanti di funzioni:

Il primo è un effetto. Queste sono le modifiche allo stato del gioco che si verificano quando giochi la carta. L'interfaccia per l'effetto è la seguente:

boolean isPlayable(Player p, GameState gs);
void play(Player p, GameState gs);

E potresti considerare la carta giocabile se e solo se puoi far fronte al suo costo e tutti i suoi effetti sono giocabili. Mi piace così:

// in Card class
boolean isPlayable(Player p, GameState gs) {
    if(p.resource < this.cost) return false;
    for(Effect e : this.effects) {
        if(!e.isPlayable(p,gs)) return false;
    }
    return true;
}

Ok, finora, piuttosto semplice.

L'altra serie di funzioni sulla carta sono abilità. Queste abilità sono modifiche allo stato del gioco che puoi attivare a volontà. Quando ho trovato l'interfaccia per questi, mi sono reso conto che avevano bisogno di un metodo per determinare se possono essere attivati o meno, e un metodo per implementare l'attivazione. Finisce per essere

boolean isActivatable(Player p, GameState gs);
void activate(Player p, GameState gs);

E mi rendo conto che con l'eccezione di chiamarlo "attiva" invece di "play", Ability e Effect hanno la stessa identica firma.

È una cosa brutta avere più interfacce con una firma identica? Dovrei semplicemente usarne uno e avere due set della stessa interfaccia? In questo modo:

Set<Effect> effects;
Set<Effect> abilities;

Se è così, quali passi refactoring dovrei prendere in mano se diventano non identici (come vengono rilasciate più funzioni), in particolare se sono divergenti (cioè entrambi guadagnano qualcosa l'altro non dovrebbe, al contrario di un solo guadagno e l'altro è un sottoinsieme completo)? Sono particolarmente preoccupato che la loro combinazione non sarà sostenibile non appena qualcosa cambierà.

La stampa fine:

Riconosco che questa domanda è generata dallo sviluppo del gioco, ma ritengo che sia il tipo di problema che potrebbe facilmente insinuarsi nello sviluppo non di gioco, in particolare quando si cerca di accogliere i modelli di business di più client in un'applicazione come succede con quasi tutti i progetti che ho mai fatto con più di un'influenza commerciale ... Inoltre, i frammenti utilizzati sono snippet Java, ma questo potrebbe facilmente applicarsi a una moltitudine di linguaggi orientati agli oggetti.

    
posta corsiKa 03.10.2012 - 18:30
fonte

4 risposte

18

Solo perché due interfacce hanno lo stesso contratto, non significa che siano la stessa interfaccia.

Principio di sostituzione di Liskov afferma:

Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

O, in altre parole: tutto ciò che è vero per un'interfaccia o un supertipo dovrebbe essere vero per tutti i suoi sottotipi.

Se comprendo correttamente la tua descrizione, un'abilità non è un effetto e un effetto non è un'abilità. Se uno dei due cambia il contratto, è improbabile che l'altro cambi con esso. Non vedo alcun motivo per cercare di legarli tra loro.

    
risposta data 03.10.2012 - 18:48
fonte
2

Da Wikipedia : "L'interfaccia è spesso usata per definire un tipo astratto che non contiene dati, ma espone comportamenti definiti come metodi ". Secondo me un'interfaccia è usata per descrivere un comportamento, quindi se hai comportamenti diversi ha senso avere interfacce diverse. Leggendo la tua domanda l'impressione che ho ottenuto è che stai parlando di comportamenti diversi, quindi interfacce diverse potrebbero essere l'approccio migliore.

Un altro punto che hai detto è che se uno di questi comportamenti cambia. Allora cosa succede quando hai una sola interfaccia?

    
risposta data 03.10.2012 - 18:48
fonte
1

Se le regole del gioco di carte fanno una distinzione tra "effetti" e "abilità", devi assicurarti che siano interfacce diverse. Questo ti impedirà di usarne accidentalmente uno in cui è richiesto l'altro.

Detto questo, se sono estremamente simili, potrebbe avere senso derivarli da un antenato comune. Considera attentamente: hai ragione di credere che "effetti" e "abilità" avranno sempre necessariamente la stessa interfaccia? Se aggiungi un elemento all'interfaccia effect , ti aspetteresti di aver bisogno dello stesso elemento aggiunto all'interfaccia ability ?

In tal caso, puoi inserire tali elementi in un'interfaccia feature comune da cui sono entrambi derivati. In caso contrario, non dovresti provare a unificarli: perderai tempo a spostare elementi tra le interfacce di base e quelle derivate. Tuttavia, dal momento che non si intende utilizzare effettivamente l'interfaccia di base comune per qualcosa tranne "non ripetersi", potrebbe non fare la stessa differenza. E, se ti attieni a questa intenzione, la mia ipotesi è che se fai la scelta sbagliata all'inizio, il refactoring per correggerlo in un secondo momento può essere relativamente semplice.

    
risposta data 03.10.2012 - 22:19
fonte
0

Quello che stai vedendo è fondamentalmente un artefatto della limitata espressività dei sistemi di tipi.

Teoricamente, se il tuo sistema di tipi ti permettesse di specificare con precisione il comportamento di queste due interfacce, le loro firme sarebbero diverse perché i loro comportamenti sono diversi. In pratica, l'espressività dei sistemi di tipo è limitata da limiti fondamentali come il problema dell'arresto e il teorema di Riso, quindi non è possibile esprimere un aspetto del comportamento ogni .

Ora, i diversi sistemi di tipi hanno diversi gradi di espressività, ma ci sarà sempre qualcosa che non può essere espresso.

Ad esempio: due interfacce con un comportamento di errore diverso possono avere la stessa firma in C #, ma non in Java (perché le eccezioni Java fanno parte della firma). Due interfacce il cui comportamento differisce solo nei loro effetti collaterali possono avere la stessa firma in Java, ma avranno firme diverse in Haskell.

Quindi, è sempre possibile che finirai con la stessa firma per comportamenti diversi. Se pensi che sia importante essere in grado di distinguere tra questi due comportamenti su più di un semplice livello nominale, devi passare a un sistema di tipo espressivo più (o diverso).

    
risposta data 04.10.2012 - 03:59
fonte

Leggi altre domande sui tag