Due interfacce con firme identiche


13

Sto tentando di modellare un gioco di carte in cui le carte hanno due importanti set di funzionalità:

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 riesci a far fronte al suo costo e tutti i suoi effetti sono giocabili. 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, abbastanza semplice.

L'altro set di funzioni sulla carta sono le abilità. Queste abilità sono modifiche allo stato del gioco che puoi attivare a piacimento. 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 "giocare", Abilitye Effecthanno la stessa identica firma.


È una brutta cosa avere più interfacce con una firma identica? Dovrei semplicemente usarne uno e avere due set della stessa interfaccia? Come così:

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

In tal caso, quali passi di refactoring dovrei intraprendere se diventano non identici (poiché vengono rilasciate più funzionalità), in particolare se sono divergenti (ovvero entrambi ottengono qualcosa che l'altro non dovrebbe, al contrario di un solo guadagno e l'altro essendo 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 insinuarsi facilmente nello sviluppo di un gioco, in particolare quando si tenta di accogliere i modelli di business di più client in un'applicazione come accade quasi ogni progetto che abbia mai fatto con più di un'influenza aziendale ... Inoltre, i frammenti utilizzati sono frammenti Java, ma questo potrebbe facilmente applicarsi a una moltitudine di linguaggi orientati agli oggetti.


Segui KISS e YAGNI e dovresti stare bene.
Bernard,

2
L'unica ragione per cui questa domanda appare anche è perché le tue funzioni hanno accesso a troppi stati, a causa dei parametri incredibilmente ampi e senza vincoli di Player e GameState.
Lars Viklund,

Risposte:


18

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

Il principio di sostituzione di Liskov afferma:

Sia q (x) una proprietà provabile per gli oggetti x di tipo T. Quindi q (y) dovrebbe essere dimostrabile per gli oggetti y di tipo S dove S è un sottotipo di T.

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

Se capisco bene la tua descrizione, un'abilità non è un effetto e un effetto non è un'abilità. Se uno dei due cambia il suo contratto, è improbabile che l'altro cambi con esso. Non vedo alcuna buona ragione per provare a legarli l'uno all'altro.


2

Da Wikipedia : "L' interfaccia viene spesso utilizzata 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 avuto è 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. Quindi cosa succede quando hai una sola interfaccia?


1

Se le regole del tuo gioco di carte fanno una distinzione tra "effetti" e "abilità", devi assicurarti che siano interfacce diverse. Ciò ti impedirà di utilizzarne uno accidentalmente laddove è richiesto l'altro.

Detto questo, se sono estremamente simili, può avere senso derivarli da un antenato comune. Considerare attentamente: hai motivo di credere che "effetti" e "abilità" avranno sempre necessariamente la stessa interfaccia? Se aggiungi un elemento effectall'interfaccia, ti aspetteresti di aggiungere lo stesso elemento abilityall'interfaccia?

In tal caso, è possibile posizionare tali elementi in un'interfaccia comune featureda cui entrambi sono derivati. In caso contrario, non dovresti provare a unificarli: perderai tempo a spostare elementi tra le interfacce di base e derivate. Tuttavia, poiché non si intende effettivamente utilizzare l'interfaccia di base comune per nulla ma "non ripetere", potrebbe non fare molta differenza. E, se ti attieni a tale intenzione, la mia ipotesi è che se fai una scelta sbagliata all'inizio, il refactoring per risolverlo in seguito potrebbe essere relativamente semplice.


0

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

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

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

Ad esempio: due interfacce con comportamenti di errore diversi possono avere la stessa firma in C #, ma non in Java (perché in Java le eccezioni 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 tu finisca con la stessa firma per comportamenti diversi. Se ritieni che sia importante essere in grado di distinguere tra questi due comportamenti a più di un semplice livello nominale, devi passare a un sistema di tipo espressivo più (o diverso).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.