Implementa più interfacce separate o gerarchia di interfacce

3

Recentemente ho refactoring uno dei miei progetti e ho preso una decisione che devo prendere.

Ho diverse interfacce:

  1. Entity : qualcosa nel mondo di gioco
  2. Actor : un Entity che può eseguire azioni
  3. Character : un Actor che ha un nome (così come alcune altre caratteristiche che non ho ancora determinato
  4. Player : A Character riprodotto da un essere umano.

La gerarchia di tipi è attualmente

public interface Entity { }
public interface Actor extends Entity { }
public interface Character extends Actor { }
public interface Player extends Character { }

Il motivo per cui l'ho progettato in questo modo è perché gran parte delle funzionalità arriverà da mod community-driven che aggiungono funzionalità al gioco, e voglio che sia il più modulare possibile.

Ora, la decisione che ho preso è: dovrei mantenere questa gerarchia di tipi, o, per consentire un design più robusto (forse, non riesco a pensare ad un esempio), implementarli individualmente?

Gli attuali dettagli di implementazione sono

public class BaseEntity implements Entity { }
public class BaseActor extends BaseEntity implements Actor { }
public class BaseCharacter extends BaseActor implements Character { }
public class BasePlayer extends BaseCharacter implements Player { }

La mia domanda è, ci sarebbe qualche vantaggio (dal punto di vista del design) di fare qualcosa del genere, invece

public interface Entity { }
public interface Actor { }
public interface Character { }
public interface Player { }

public class BaseEntity implements Entity { }
public class BaseActor implements Entity, Actor { }
public class BaseCharacter implements Entity, Actor, Character { }
public class BasePlayer implements Entity, Actor, Character, Player { }

Non riesco a pensare a una buona ragione per farlo, ma qualcuno che userebbe la mia API per creare il proprio mod potrebbe avere il proprio

public class FooPlayer implements Player, Foo { }

E non voglio / preoccuparti dei metodi definiti in Entity , Actor o Character .

    
posta Zymus 25.02.2015 - 05:47
fonte

5 risposte

3

La decisione di estendere l'interfaccia a un'altra dovrebbe basarsi sul Principio di sostituzione di Liskov . Pertanto, Actor deve estendere solo Entity se anche ogni programma corretto quando viene fornito un Entity sarà corretto quando viene dato un Actor . Se non sempre accade che un Actor può implementare tutto ciò che dovrebbe essere implementato da Entity , quindi tenerli separati. Vedi anche il principio di segregazione dell'interfaccia .

Se si decide di mantenere separate le interfacce, è necessario utilizzare i generici ogni volta che i metodi richiedono un argomento che implementa più interfacce. Per es.

public <T extends Entity & Actor> void foo(T entityActor) {
    entityActor.someEntityMethod(); // OK
    entityActor.someActorMethod(); // Also OK
}

L'unico costo qui è la verbosità della firma, ma per il resto non è più difficile da implementare o utilizzare:

BaseActor actor = getActorFromSomewhere();
foo(actor); // Just works

Nel caso di interfacce separate, raccomanderei anche di scegliere la composizione e la delega sull'ereditarietà, come illustrato nella risposta di Thomas Junk. Le gerarchie di ereditarietà sono fragili e rigide; utilizzando la composizione e la delega è facile combinare le implementazioni delle diverse interfacce.

    
risposta data 25.02.2015 - 15:41
fonte
2

L'approccio "qualcosa come questo" non funziona nemmeno:

Se passi un'interfaccia Player a una funzione, la funzione non avrà accesso a Character , o Actor o alla funzione Entity . Presumibilmente il Entity contiene cose molto importanti, come la posizione dell'entità nel mondo, quindi avrai un Player nelle tue mani e non sarai in grado di dire dove sia nel mondo.

Il tuo approccio iniziale è un ottimo modo per andare.

Le gerarchie di interfaccia parallele alle gerarchie di oggetti sono abbastanza comuni. Ad esempio, vedi le interfacce delle raccolte di java e le classi di implementazione. L'interfaccia NavigableMap estende l'interfaccia SortedMap , che a sua volta estende l'interfaccia Map . (E in linguaggi leggermente più elaborati, come C #, l'interfaccia Map non esita dall'estensione dell'interfaccia Collection .) È perfettamente valida e molto utilizzabile.

Modifica

Se vuoi esaminare un approccio alternativo, considera di studiare il modello a oggetti di Unity , che non si basa tanto sull'ereditarietà degli oggetti e le gerarchie di interfacce, e invece usa il concetto di componenti. Ecco un'introduzione: link

    
risposta data 25.02.2015 - 09:06
fonte
0

Devo immaginare che il tuo primo modo, cioè, dove BaseActor estende BaseEntity e implementa l'attore, sarebbe la scelta preferibile. Avere le implementazioni estendere un'implementazione dell'interfaccia genitore consentirebbe di ridurre la duplicazione del codice, poiché, ad esempio, BaseActor può ereditare le implementazioni di base dei metodi in Entity da BaseEntity. Se non c'è motivo di cambiare ciò che stanno facendo da come sono definiti in BaseEntity, perché ridefinirli ancora e ancora in ogni classe nella gerarchia?

    
risposta data 25.02.2015 - 06:19
fonte
0

Questo dipende da quali metodi sono presenti su Player e se è necessario aggiungere metodi migliori su altre interfacce.

es. getName (). Se vuoi chiamare getName () su un Player, dovresti definire il metodo lì come in Character.

Se questo non è il caso, dovresti chiedere se Player // è un // personaggio dopotutto.

Nel complesso, è meglio se le tue classi modellano correttamente il dominio e io romperò la gerarchia solo se non è realmente nel dominio (se un giocatore non è realmente un personaggio).

Un esempio di quando ciò potrebbe accadere è quando esiste un'eredità solo per salvare il codice.

Ad esempio, potresti volere un Veicolo che può guidare in modo da estendere BaseActor per ottenere alcuni comportamenti quando un'automobile è solo un'entità in quanto è l'attore che lo usa a compiere azioni. (Quindi dovresti probabilmente comporre un attore nel tuo veicolo che estende BaseEntity).

    
risposta data 25.02.2015 - 08:47
fonte
0

Vorrei andare per un altro approccio:

Interfaces è pensato per condividere il comportamento attraverso oggetti altrimenti indipendenti o diversi. Per esempio. se hai un'interfaccia chiamata swim potrebbe essere implementata da umani o da anatre . Anatre e umani non hanno molto in comune, ma: potrebbero nuotare; e se guardi come gli umani e le anatre lo fanno, usano un approccio diverso, o implementazione . Quindi, se hai a che fare con humans e ducks , è assolutamente logico utilizzare un'interfaccia. Questo è chiamato il modello di strategia .

Se vuoi esprimere, che i soggetti di qualche comportamento hanno qualcosa in comune, puoi usare l'ereditarietà (ical) di classe o come dicono i libri: »è un« - rapporto .

Fin qui la teoria.

Guardando ciò che hai postato, hai diverse interfacce:

public interface Entity { }
public interface Actor extends Entity { }
public interface Character extends Actor { }
public interface Player extends Character { }

Entity: Something in the game world

Actor: An Entity that can perform actions

Character: An Actor that has a name (as well as some other features I haven't determined yet

Player: A Character that is played by a human being.

Le tue classi sono BaseEntity , BaseActor , BaseCharacter , BasePlayer .

Si sta eseguendo il mirroring delle definizioni di classe nelle definizioni dell'interfaccia. Qui, stai dicendo qualcosa di simile al seguente: » Un'entità è, cosa ha un comportamento-entità « Solo in parte, questo ha senso. Cioè, ciò che mi aspetto da un'entità.

Sulla base, da quello che ho detto, che oggetti diversi condividono il comportamento tramite interfacce, la tua decisione sembra al meglio indecisa:

1) Se si sceglie di utilizzare un approccio gerarchico tramite ereditarietà ( is-a ), non è necessario eseguire il backup di ogni classe con un'interfaccia. Hai oggetti simili (un attore è un'entità con alcuni extra, un personaggio è in linea di principio un attore con alcuni extra). Se hai bisogno di specifiche, di solito dividi le sottoclassi.

2) O scegli l'approccio " comportamento tramite interfaccia ", dove definisci comportamento (e forse gruppi di comportamento ) ma non ho bisogno di classhierarchy.

Ma non entrambi.

Il tuo secondo approccio va in quella direzione di (2).

public interface Entity { }

public interface Actor { }

public interface Character { }

public interface Player { }

public class BaseEntity implements Entity { }

public class BaseActor implements Entity, Actor { }

public class BaseCharacter implements Entity, Actor, Character { }

public class BasePlayer implements Entity, Actor, Character, Player { }

Meglio:

public interface Entity { }
public interface Actor extends Entity{ }
public interface Character extends Actor{ }
public interface Player extends Character{ }

public class EntityImpl implements Entity { }
public class ActorImpl implements Actor { }
public class CharacterImpl implements Character { }
public class PlayerImpl implements Player { }

Con questo, si rendono chiari i seguenti punti:

1) Per ogni classe, definisci rigorosamente l'API ("interfacce come contratti"): Un "carattere" è un oggetto, che si comporta almeno come un "Carattere"

2) Poiché Character estende Actor, stai dicendo che i personaggi in qualche modo si comportano come attori

3) Se voglio progettare un'entità con degli extra, potrei farlo utilizzando l'interfaccia esistente e creare una nuova classe implementandola e aggiungendo nuovi comportamenti (magari tramite un'altra interfaccia). Quindi le interfacce sono riutilizzabili.

Naturalmente, c'è qualche svantaggio: le interfacce devono essere implementate con ogni classe (eccezione: metodi predefiniti ). D'altro canto, si vede ogni metodo (_implementazione) senza risalire l'albero dell'eredità.

Potresti scegliere in entrambi i modi, gerarchia di classi o gerarchia di interfacce . In termini di manutenibilità e flessibilità, opterei per la seconda soluzione.

    
risposta data 25.02.2015 - 14:26
fonte

Leggi altre domande sui tag