Domanda

Diciamo che mi viene assegnato il compito di programmare una sorta di gioco di ruolo.Ciò significa che, ad esempio, vorrò monitorare a Character GameCharacter e le relative statistiche, come intelligenza, bonus ai danni o punti ferita.

Ho decisamente paura che entro la fine del progetto potrei ritrovarmi a gestire un numero molto elevato di campi - e per ciascuno dovrei assicurarmi che seguano un insieme di vincoli e comportamenti molto simili (ad esempio, io voglio che siano limitati tra un minimo e un massimo;Voglio poter distinguere tra un “valore base” e un “bonus temporaneo”;Voglio essere in grado di incrementare e decrementare entrambi senza passare attraverso setter e getter).All'improvviso, per ogni campo avrei bisogno di un (due?) getter e quattro setter e forse anche un paio di resetter!Anche per 10 campi significa MOLTI metodi tutti uguali, eek.

Per DRYness ho iniziato a incapsulare la logica con cui si scherza con quelle statistiche Field classi, in modo da poter scrivere codice come intelligence.applyBonus(10) O hitpoints.get() (che si prende cura che il valore restituito sia nell'intervallo), ecc.Ho persino fatto di tutto per creare classi per raggruppare insieme questi campi, ma non è questo il punto in questo momento.

Ora, ho riscontrato questo problema mentre "collegavo" Field in GameCharacter:la maggior parte dei libri di testo Java afferma che ogni classe dovrebbe avere campi privati ​​con getter e setter pubblici.In teoria suona bene e ho già costruito un'intera classe attorno ad an int;tuttavia, l'idea non sembra così solida quando ti ritrovi a chiamare un getter per ottenere...un getter:

thisCharacter.getIntelligence().get() //eeek

Preferirei di gran lunga accedere direttamente al campo.Forse è il mio " background " di Python/VB [1], ma per me è più pulito, più chiaro e più diretto:

thisCharacter.intelligence.get()

Il problema (teorico) con i campi pubblici è che sto rinunciando a ogni controllo su di essi;ad esempio in qualche altro punto del codice base, per sfortuna, potrebbe accadere quanto segue:

thisCharacter.intelligence = somethingThatReallyIsNull;

Sembra un bug sottile...Ma...Voglio dire, dovrei davvero preoccuparmene?Per quanto mi riguarda, non ho mai intenzione di assegnare il file Field direttamente [2], ho documentato nel Javadoc che questo non è qualcosa che dovrebbe essere fatto, ma sono comunque nuovo qui, quindi sono un po' combattuto.

Quindi mi piacerebbe sapere cosa ne pensi di questo argomento.I vantaggi dell'incapsulamento sono così enormi che dovrei andare avanti e avere getter getter e setter getter e così via...o dovrei prendere l'incapsulamento in misure salutari e lasciare il Field come un public campo?


[1] Sì, lo so.Ho cercato di dimenticare.Ma di recente abbiamo visto anche un po' di C# e, cavolo, non sono proprietà carine.Vabbè.

[2] tranne che nei Costruttori!E un getter non mi salverà da un costruttore difettoso.

È stato utile?

Soluzione

La mia esperienza è che in situazioni in cui è necessario a quantità di campi, il numero, la natura, la denominazione e i tipi di campi sono così flessibili e suscettibili di cambiare nel corso della vita del progetto che probabilmente avresti bisogno di una sorta di mappa invece dei campi.

Ad esempio, disponi di una mappa degli attributi dalle chiavi ai valori.

Fornire chiamate pubbliche per ottenere e impostare gli attributi, ma non consentire a tutti di usarli (o assicurarsi che non lo facciano).Crea invece classi per rappresentare ogni attributo che ti interessa e quella classe fornirà tutte le funzioni per manipolare quell'attributo.Ad esempio, se hai Forza, potresti avere una classe "StrengthManipulation" che viene inizializzata su un oggetto Player specifico e quindi fornisce getter, setter (tutti con validazione ed eccezioni appropriate) e forse cose come il calcolo della forza con bonus, ecc. .

Un vantaggio di ciò è che disaccoppia l'uso dei tuoi attributi dalla classe del tuo giocatore.Quindi, se ora aggiungi un attributo Intelligenza, non devi gestire e ricompilare tutto ciò che manipola solo la forza.

Per quanto riguarda l'accesso diretto ai campi, è una cattiva idea.Quando accedi a un campo in VB (almeno nei vecchi VB), di solito chiami una proprietà getter e setter e VB nasconde semplicemente la chiamata ().La mia opinione è che devi adattarti alle convenzioni della lingua che stai utilizzando.In C, C++, Java e simili hai campi e metodi.La chiamata a un metodo dovrebbe sempre avere il segno () per chiarire che si tratta di una chiamata e che potrebbero verificarsi altre cose (ad esempio, potresti ottenere un'eccezione).In ogni caso, uno dei vantaggi di Java è la sintassi e lo stile più precisi.

Da VB a Java o C++ è come mandare SMS alla scrittura scientifica della scuola di specializzazione.

A proposito:Alcune ricerche sull'usabilità mostrano che è meglio non avere parametri per i costruttori e piuttosto costruire e chiamare tutti i setter se ne hai bisogno.

Altri suggerimenti

Sembra che tu stia pensando in termini di if(player.dexterity > monster.dexterity) attacker = player.Devi pensare di più if(player.quickerThan(monster)) monster.suffersAttackFrom(player.getCurrentWeapon()).Non scherzare con le semplici statistiche, esprimi la tua reale intenzione e poi progetta le tue lezioni in base a ciò che dovrebbero fare.Le statistiche sono comunque una scappatoia;ciò che ti interessa veramente è se un giocatore può o non può eseguire qualche azione, o la sua abilità/capacità rispetto a qualche riferimento.Pensa in termini di "il personaggio del giocatore è abbastanza forte" (player.canOpen(trapDoor)) invece di "il personaggio ha almeno 50 punti di forza".

Steve Yegge ha scritto un post sul blog molto interessante (anche se lungo) che trattava questi problemi: Il modello di progettazione universale.

A me sembra che "questo personaggio" potrebbe avere un oggetto "intelligence" per gestire l'intelligence dietro le quinte, ma mi chiedo se dovrebbe essere pubblico.Dovresti semplicemente esporre thisCharacter.applyInt e thisCharacter.getInt invece dell'oggetto che si occupa di esso.Non esporre la tua implementazione in questo modo.

Mantieni i tuoi campi privati!Non vuoi mai esporre troppo della tua API.Puoi sempre rendere pubblico qualcosa che era privato, ma non il contrario, nelle versioni future.

Pensa come se lo inserirai nel prossimo MMORPG.Avresti molto spazio per bug, errori e male inutili.Assicurarsi che le proprietà immutabili siano definitive.

Pensa a un lettore DVD, con la sua interfaccia miniamilistica (riproduzione, stop, menu), eppure con tanta tecnicità al suo interno.Ti consigliamo di nascondere tutto ciò che non è vitale nel tuo programma.

Sembra che la tua lamentela principale non riguardi tanto l'astrazione dei metodi setter/getter, ma la sintassi del linguaggio per usarli.Cioè, preferiresti qualcosa come le proprietà in stile C#.

Se è così, allora il linguaggio Java ha relativamente poco da offrirti.L'accesso diretto al campo va bene, finché non avrai bisogno di passare a un getter o setter, e poi avrai qualche refactoring da fare (possibilmente ok, se controlli l'intera base di codice).

Naturalmente, se la piattaforma Java è un requisito, ma il linguaggio no, allora ci sono altre alternative.Scala ha una sintassi delle proprietà molto bella, ad esempio, insieme a molte altre funzionalità che potrebbero essere utili per un progetto del genere.E soprattutto, funziona su JVM, quindi ottieni comunque la stessa portabilità che otterresti scrivendolo nel linguaggio Java.:)

Quello che sembra avere qui è un singolo strato di un modello composito.

Potresti voler aggiungere metodi che aggiungano astrazioni al modello, anziché averlo semplicemente come un insieme di modelli di livello inferiore.

I campi dovrebbero essere definitivi, quindi anche se li rendessi pubblici non potresti assegnarli accidentalmente null a loro.

Il prefisso "get" è presente per tutti i getter, quindi probabilmente è più l'aspetto iniziale che un nuovo problema in quanto tale.

I vantaggi dell'incapsulamento sono così enormi che dovrei andare avanti e avere getter getter e setter getter e così via...o dovrei adottare misure salutari per l’incapsulamento e lasciare il Campo come campo pubblico?

Secondo me, l'incapsulamento non ha nulla a che fare con l'avvolgimento di un getter/setter attorno a un campo privato.A piccole dosi o quando si scrivono librerie di uso generale, il compromesso è accettabile.Ma se lasciato senza controllo in un sistema come quello che stai descrivendo, è un an antimodello.

Il problema con getter/setter è che creano un accoppiamento eccessivamente stretto tra l'oggetto con quei metodi e il resto del sistema.

Uno dei vantaggi dell'incapsulamento reale è che riduce la necessità di getter e setter, disaccoppiando l'oggetto dal resto del sistema nel processo.

Piuttosto che esporre l'implementazione di GameCharacter con setIntelligence, perché non dare a GameCharacter un'interfaccia che rifletta meglio il suo ruolo nel sistema di gioco?

Ad esempio, invece di:

// pseudo-encapsulation anti-pattern
public class GameCharacter
{
  private Intelligence intelligence;

  public Intelligence getIntelligence()
  {
    return intelligence
  }

  public void setIntelligence(Intelligence intelligence)
  {
    this.intelligence = intelligence;
  }
}

perché non provare questo?:

// better encapsulation
public class GameCharacter
{
  public void grabObject(GameObject object)
  {
    // TODO update intelligence, etc.
  }

  public int getIntelligence()
  {
    // TODO
  }
}

o meglio ancora:

// still better
public interface GameCharacter
{
  public void grabObject(GameObject object); // might update intelligence
  public int getIntelligence();
}

public class Ogre implements GameCharacter
{
  // TODO: never increases intelligence after grabbing objects
}

In altre parole, un GameCharacter può afferrare GameObjects.L'effetto di ogni GameCharacter che cattura lo stesso GameObject può (e dovrebbe) variare, ma i dettagli sono completamente incapsulati all'interno di ciascuna implementazione di GameCharacter.

Nota come GameCharacter è ora incaricato di gestire il proprio aggiornamento di intelligenza (controllo della portata, ecc.), cosa che può accadere mentre afferra GameObjects, ad esempio.Il setter (e le complicazioni che noti nell'averlo) sono scomparsi.A seconda della situazione è possibile rinunciare del tutto al metodo getIntelligence.Allen Holub porta avanti questa idea conclusione logica, ma questo approccio non sembra essere molto comune.

Oltre alla risposta di Uri (che sostengo totalmente), vorrei suggerirti di considerare la definizione della mappa degli attributi nei dati.Ciò renderà il tuo programma ESTREMAMENTE flessibile e eliminerà molto codice di cui non ti rendi nemmeno conto di non aver bisogno.

Ad esempio, un attributo può sapere a quale campo si collega sullo schermo, a quale campo si collega nel database, un elenco di azioni da eseguire quando l'attributo cambia (un ricalcolo della percentuale di successo potrebbe applicarsi sia alla forza che alla destrezza... )

In questo modo, NON hai codice per attributo per scrivere una classe nel DB o visualizzarla sullo schermo.Basta scorrere gli attributi e utilizzare le informazioni memorizzate all'interno.

La stessa cosa può applicarsi alle abilità: infatti, attributi e abilità deriverebbero probabilmente dalla stessa classe base.

Dopo aver percorso questa strada, probabilmente ti ritroverai con un file di testo piuttosto serio per definire attributi e abilità, ma aggiungere una nuova abilità sarebbe facile come:

Skill: Weaving
  DBName: BasketWeavingSkill
  DisplayLocation: 102, 20  #using coordinates probably isn't the best idea.
  Requires: Int=8
  Requires: Dex=12
  MaxLevel=50

Ad un certo punto, l'aggiunta di un'abilità come questa non richiederebbe alcuna modifica del codice, tutto potrebbe essere fatto abbastanza facilmente nei dati e TUTTI i dati vengono archiviati in un singolo oggetto abilità collegato a una classe.Ovviamente potresti definire le azioni nello stesso modo:

Action: Weave Basket
  text: You attempt to weave a basket from straw
  materials: Straw
  case Weaving < 1
    test: You don't have the skill!
  case Weaving < 10
    text: You make a lame basket
    subtract 10 straw
    create basket value 8
    improve skill weaving 1%
  case Weaving < 40
    text: You make a decent basket
    subtract 10 straw
    create basket value 30
    improve skill weaving 0.1%
  case Weaving < 50
    text: You make an awesome basket!
    subtract 10 straw
    create basket value 100
    improve skill weaving 0.01%
  case Weaving = 50
    text: OMG, you made the basket of the gods!
    subtract 10 straw
    create basket value 1000

Sebbene questo esempio sia piuttosto avanzato, dovresti essere in grado di visualizzare come verrebbe fatto senza alcun codice.Immagina quanto sarebbe difficile fare qualcosa del genere senza codice se i tuoi "attributi/abilità" fossero in realtà variabili membro.

Prendi in considerazione l'utilizzo di un framework di "modellazione", come EMF di Eclipse.Ciò comporta la definizione dei tuoi oggetti usando qualcosa come Eclipse EMF Editor, o in semplice XML o in rosa razionale.Non è così difficile come sembra.Tu definisci attributi, vincoli, ecc.Poi il quadro genera il codice per te.Aggiunge i tag @generated alle parti che ha aggiunto come metodi getter e setter.È possibile personalizzare il codice, e successivamente modificare manualmente o tramite qualche interfaccia grafica, e rigenerare i file java.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top