Pergunta

Vamos dizer que estou encarregado de codificação de algum tipo de um RPG. Isto significa que, por exemplo, eu vou deseja acompanhar um Character GameCharacter e estatísticas dela, como inteligência, bônus de dano ou pontos de vida.

Estou com medo positivamente que até o final do projeto que pode acabar com a manipulação com muito um alto número de campos - e para cada eu teria que se certificar de que eles seguem um conjunto muito similar de restrição e comportamentos (por exemplo, eu quero que eles sejam delimitadas entre um minuto e um máximo; eu quero ser capaz de distinguir entre um "valor base" e um "bônus temporário", eu quero ser capaz de aumentar e diminuir tanto sem passar por um setters e getters). De repente, para cada campo eu precisaria de um (dois?) Getter e quatro setters e talvez um par resetters também! Mesmo para 10 campos que significa um monte de métodos todos iguais, eek.

Para secura Eu comecei a encapsular a lógica de mexer com essas estatísticas em aulas Field, para que eu pudesse escrever código como intelligence.applyBonus(10) ou hitpoints.get() (que cuida do valor retornado está na faixa), etc. I têm mesmo ido para tal comprimento para criar classes para agrupar esses campos juntos, mas isso não é o ponto agora.

Agora, eu bati neste problema durante a "ligar" Field em GameCharacter: a maioria dos livros de Java dizer que cada classe deve ter campos privados com getters públicas e setters. Isso parece bom em teoria, e eu já construiu toda uma classe em torno de um int; no entanto, a idéia não soa tão sólida quando você encontrar-se chamar um getter para obter ... um getter:

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

Eu prefiro acessar o campo diretamente. Talvez seja o meu Python / VB [1] "background", mas para mim é mais limpa, mais clara e mais simples:

thisCharacter.intelligence.get()

O problema (teórico) com campos públicos é que eu estou desistindo todo o controle sobre ele; por exemplo, em algum outro ponto na base de código, por infelicidade, o seguinte pode acontecer:

thisCharacter.intelligence = somethingThatReallyIsNull;

soa como um bug sutil ... mas ... Quer dizer, eu deveria realmente se preocupar com isso? Eu, pelo menos nunca plano para atribuir o Field diretamente [2], eu tenho documentado no Javadoc que isso não é algo que deve ser feito, mas eu ainda sou novo aqui por isso estou um pouco devastado.

Então, eu gostaria de ouvir o que a sua opinião sobre este tema. São as vantagens de encapsulamento tão grande que eu deveria ir adiante e ter getters Getter e getters setter e assim por diante ... ou devo tomar encapsulamento em medidas saudáveis ??e deixam o Field como um campo public?


[1] Sim, eu sei. Eu tenho tentado esquecer. Mas nós só recentemente viu também um pouco de C # e homem, não são propriedades doces. Oh bem.

[2] exceto em Construtores! E um getter não vai me salvar de um construtor com defeito.

Foi útil?

Solução

A minha experiência é que em situações onde você precisa de um muito de campos, o número, natureza, nomeando, e tipos de campos são tão flexíveis e susceptíveis de mudança ao longo do tempo de vida de seu projeto que você provavelmente precisa de algum tipo de mapa em vez de campos.

Por exemplo ter um mapa de atributos de chaves para valores.

Fornecer chamadas públicas para obter e definir os atributos, mas não deixe que uso todos eles (ou se certificar que não fazer). Em vez disso, criar classes para representar cada atributo que você está interessado, e essa classe fornece todas as funções para manipular esse atributo. Por exemplo, se você tem a força, você poderia ter uma classe "StrengthManipulation" que é inicializado para um objeto Jogador específica, e em seguida, fornece getters, setters (todos com validação e exceções apropriado), e talvez coisas como calcular a força com bônus, etc .

Uma vantagem disso é que você dissociar o uso de seus atributos de sua classe jogador. Então, se você agora adicionar um atributo Intelligence, você não tem que lidar e recompilar tudo o que manipula única força.

Como para acessar campos diretamente, é uma má idéia. Quando você acessar um campo em VB (pelo menos em VBs velhos), você geralmente chamar uma propriedade getter e setter e VB simplesmente esconde a chamada () para você. Minha opinião é que você tem que se adaptar às convenções da linguagem que você está usando. Em C, C ++, Java e como você tem campos e você tem métodos. Chamar um método deve ter sempre a () para deixar claro que é uma chamada e outras coisas podem acontecer (por exemplo, você pode obter uma exceção). De qualquer forma, um dos benefícios do Java é sua sintaxe e estilo mais preciso.

VB para Java ou C ++ é como Texting à escrita científica pós-graduação.

BTW: Alguns programas de pesquisa de usabilidade que é melhor para não ter parâmetros para construtores e sim construir e chamar todos os setters se você precisar deles.

Outras dicas

Parece que você está pensando em termos de if(player.dexterity > monster.dexterity) attacker = player. Você precisa pensar mais como if(player.quickerThan(monster)) monster.suffersAttackFrom(player.getCurrentWeapon()). Não mexa com estatísticas nuas, expressar a sua intenção real e, em seguida, projetar suas classes em torno do que é suposto fazer. Estatísticas são um cop-out de qualquer maneira; o que você realmente se preocupam é se um jogador pode ou não pode fazer alguma ação, ou sua capacidade / capacidade contra alguma referência. Pense em termos de "é o personagem do jogador forte o suficiente" (player.canOpen(trapDoor)) em vez de "faz o personagem tem pelo menos 50 força".

Steve Yegge teve um muito interessante (se demorado) postagem no blog que cobria esses problemas: o padrão de design Universal .

Para mim parece que 'thisCharacter' poderia muito bem ter um objeto 'inteligência' para lidar com a inteligência por trás das cenas, mas eu questão de saber se isso deveria ser pública em tudo. Você deve apenas expor thisCharacter.applyInt e thisCharacter.getInt em vez do objeto que lida com ele. Não exponha a sua implementação assim.

Mantenha seus campos particulares! Você nunca quer expor muito de sua API. Você sempre pode fazer algo que era público-privada, mas não o contrário, em versões futuras.

Pense como se você vai fazer isso para o próximo MMORPG. Você teria muita margem para erros, erros e mal desnecessário. Certifique-se de propriedades imutáveis ??são finais.

Pense em um leitor de DVD, com sua miniamilistic de interface (play, stop, menu), e ainda tal dentro de tecnicismo. Você vai querer esconder tudo não-vital em seu programa.

Parece que a sua queixa principal não é tanto com a abstração de métodos setter / getter, mas com a sintaxe da linguagem para usá-los. Ou seja, você preferir algo como C Propriedades # estilo.

Se este for o caso, então a linguagem Java tem relativamente pouco a lhe oferecer. o acesso de campo direto é bom, até que você precisa para mudar para um getter ou setter, e então você vai ter algum refactoring para fazer (possivelmente ok, se você controla toda a base de código).

Claro que, se a plataforma Java é um requisito, mas a língua não é, em seguida, existem outras alternativas. Scala tem uma sintaxe propriedade muito agradável, por exemplo, juntamente com muitas outras características que poderiam ser úteis para esse projecto a. E o melhor de tudo, ele é executado na JVM, então você ainda obter a mesma portabilidade que você deseja obter por escrito na linguagem Java. :)

O que você parece ter aqui é uma única camada de um modelo composto.

Você pode querer adicionar métodos que adicionam abstrações ao modelo, ao invés de apenas tê-lo como um conjunto de modelos de nível mais baixo.

Os campos devem ser final, por isso mesmo se você fez torná-los públicos você não poderia acidentalmente atribuir null a eles.

O "ficar" prefixo está presente para todos os getters, por isso é provavelmente mais a aparência inicial de um novo problema como tal.

são as vantagens de encapsulamento tão grande que eu deveria ir adiante e ter getters Getter e getters setter e assim por diante ... ou devo tomar encapsulamento em medidas saudáveis ??e deixar o campo como um campo público?

IMO, o encapsulamento não tem nada a ver com o envolvimento de um getter / setter em torno de um campo privado. Em pequenas doses, ou quando se escreve bibliotecas de uso geral, a desvantagem é aceitável. Mas quando não for devidamente controlado, em um sistema como o que você está descrevendo, seu um um antipattern .

O problema com getters / setters é que eles criam acoplamento excessivamente apertado entre o objeto com esses métodos e o resto do sistema.

Uma das vantagens de encapsulamento real é que ela reduz a necessidade de getters e setters, dissociando o objeto do resto do sistema no processo.

Ao invés de expor a implementação de GameCharacter com setIntelligence, porque não dar GameCharacter uma interface que reflete melhor o seu papel no sistema de jogo?

Por exemplo, em vez de:

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

  public Intelligence getIntelligence()
  {
    return intelligence
  }

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

por que não tentar isso:?

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

  public int getIntelligence()
  {
    // TODO
  }
}

ou ainda melhor:

// 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
}

Em outras palavras, um GameCharacter pode pegar GameObjects. O efeito de cada GameCharacter agarrando o mesmo GameObject pode (e deve) variam, mas os detalhes são totalmente encapsulado dentro de cada aplicação GameCharacter.

Observe como o GameCharacter é agora responsável de lidar com a sua própria atualização de inteligência (intervalo de verificação, etc), o que pode acontecer, uma vez que agarra GameObjects, por exemplo. O setter (e as complicações que você nota com tê-lo) desapareceram. Você pode ser capaz de dispensar o método getIntelligence completamente, dependendo da situação. Allen Holub leva essa idéia ao seu conclusão lógica , mas, mas essa abordagem não parece ser muito comum.

Além de resposta de Uri (que eu apoio totalmente), eu gostaria de sugerir que você considere a definição do mapa de atributos nos dados. Isso fará com que seu programa extremamente flexível e vai levar um monte de código que você nem sequer percebem que você não precisa.

Por exemplo, um atributo pode saber o campo se liga a na tela, o que campo se liga a no banco de dados, uma lista de acções a executar quando as alterações de atributo (a recalcular hit% pode se aplicar a força e dex ...)

Fazendo dessa forma, você tem o código NO per atributo para escrever uma classe para o DB ou exibi-lo na tela. Você simplesmente iterar sobre os atributos e usar as informações armazenadas dentro.

A mesma coisa pode ser aplicada a habilidades -. Na verdade, atributos e habilidades provavelmente derivam da mesma classe base

Depois de ir por este caminho, você provavelmente vai encontrar-se com um arquivo de texto sério bastante para definir atributos e habilidades, mas acrescentando uma nova habilidade seria tão fácil como:

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

Em algum momento, a adição de uma habilidade como isso exigiria nenhuma mudança de código que seja, tudo pode ser feito em dados muito facilmente, e todos os dados são armazenados em um único objeto habilidade ligado a uma classe. Você poderia, é claro, definir ações da mesma maneira:

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

Embora este exemplo é bastante avançado, você deve ser capaz de visualizar como seria feito com absolutamente nenhum código. Imagine como seria difícil fazer qualquer coisa assim sem código se seus "atributos / habilidades" eram na verdade variáveis ??de membro.

Considere o uso de um quadro "modelagem", como EMF Eclipse. Isso envolve a definição de seus objetos usando algo como editor Eclipse EMF, ou em XML simples, ou no Rational Rose. Não é tão difícil quanto parece. Você definir atributos, restrições etc. Então o quadro gera o código para você. Ele adiciona marcas @generated às partes que agregado, como get e set métodos. Você pode personalizar o código, e depois editar -lo à mão ou através de algum GUI, e regenerar os arquivos Java.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top