2009-04-20 9 views
9

Digamos que tengo la tarea de codificar algún tipo de juego de rol. Esto significa que, por ejemplo, deseo rastrear un CharacterGameCharacter y sus estadísticas, como inteligencia, bonificaciones por daños o puntos de vida.Java: cómo manejar MUCHOS campos y su encapsulación limpiamente?

Tengo mucho miedo de que, al final del proyecto, pueda terminar manejando un gran número de campos, y para cada uno de ellos tendría que asegurarme de que siguen un conjunto muy similar de restricciones y comportamientos (por ejemplo, quiero que estén limitados entre un mínimo y un máximo; quiero ser capaz de distinguir entre un "valor base" y un "bonus temporal"; quiero poder aumentar y disminuir ambos sin pasar por un setters y getters). De repente, para cada campo necesitaría uno (dos?) Getter y cuatro setters y tal vez un par de restablecedores también! Incluso para 10 campos eso significa MUCHOS métodos todos iguales, eek.

para la sequedad que han comenzado encapsular la lógica de jugar con esas estadísticas en Field clases, por lo que podría escribir código como intelligence.applyBonus(10) o hitpoints.get() (que se encarga el valor devuelto es en el rango), etc. Tengo incluso se ha ido a tal longitud para crear clases para agrupar esos campos juntos, pero ese no es el punto en este momento.

Ahora, me tocó en este problema al "conectar" Field en GameCharacter: la mayoría de los libros de texto de Java dicen que cada clase debe tener campos privados con getters y setters públicos. Eso suena bien en teoría, y ya he construido una clase completa alrededor de un int; Sin embargo, la idea no suena tan sólida cuando usted se encuentra llamando a un captador de conseguir ... un captador:

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

yo preferiría tener acceso al campo directamente. Tal vez sea mi Python/VB [1] "fondo", pero para mí es más limpio, más claro y sencillo:

thisCharacter.intelligence.get() 

El problema (teórico) con campos públicos es que estoy dando todo el control sobre el eso; por ejemplo, en algún otro punto en el código base, por desgracia, lo siguiente puede ocurrir:

thisCharacter.intelligence = somethingThatReallyIsNull; 

suena como un error sutil ... pero ... quiero decir, realmente debería preocuparse por eso? Yo nunca pensé asignar el Field directamente [2], he documentado en el Javadoc que esto no es algo que deba hacerse, pero igual soy nuevo aquí, así que estoy un poco desgarrado.

Así que me gustaría saber cuál es su opinión sobre este tema. ¿Son las ventajas de la encapsulación tan masivas que debería seguir adelante y tener get get getters y setter getters y demás ... o debería tomar encapsulación en medidas saludables y dejar el Field como un campo public?


[1] Sí, lo sé. He estado tratando de olvidar. Pero recientemente hemos visto también un poco de C# y hombre, no son propiedades dulces. Oh bien.

[2] excepto en constructores! Y un getter no me salvará de un constructor defectuoso.

Respuesta

8

Mi experiencia es que en situaciones en las que se necesita un mucho de campos, el número, la naturaleza, denominación, y tipos de campos son tan flexibles y pueden cambiar durante la vida útil de su proyecto que usted probablemente necesitará alguna tipo de mapa en lugar de campos.

Por ejemplo, tienen un mapa de atributos de las claves a los valores.

Ofrezca llamadas públicas para obtener y configurar los atributos, pero no permita que todos los usen (o asegúrese de que no los usen). En su lugar, cree clases para representar cada atributo que le interese, y esa clase proporciona todas las funciones para manipular ese atributo. Por ejemplo, si tiene Fuerza, podría tener una clase "StrengthManipulation" que se inicializa a un objeto Player específico, y luego proporciona getters, setters (Todos con validación y excepciones apropiadas), y tal vez cosas como calcular la fuerza con bonificaciones, etc.

Una ventaja de esto es que desacopla el uso de sus atributos de su clase de jugador. Entonces, si ahora agrega un atributo de Inteligencia, no tiene que tratar y recompilar todo lo que manipula solo la fuerza.

En cuanto al acceso a los campos directamente, es una mala idea. Cuando accede a un campo en VB (al menos en VB antiguos), normalmente llama a un getter y setter de propiedades y VB simplemente oculta la llamada() por usted. Mi punto de vista es que debes adaptarte a las convenciones del idioma que estás usando. En C, C++, Java y similares tiene campos y tiene métodos. Llamar a un método siempre debe tener el() para dejar en claro que es una llamada y que pueden ocurrir otras cosas (por ejemplo, podría obtener una excepción). De cualquier manera, uno de los beneficios de Java es su sintaxis y estilo más precisos.

VB a Java o C++ es como enviar mensajes de texto a la escritura científica de la escuela de postgrado.

BTW: Algunas investigaciones de usabilidad muestran que es mejor no tener parámetros para los constructores y más bien construir y llamar a todos los instaladores si los necesita.

+0

Estoy de acuerdo con Uri. Tener un mapa es mucho más fácil de mantener y escalar, en lugar de usar campos. –

+1

cf, The Universal Design Pattern: http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html – jsight

+0

Siempre he pensado que es una expresión bastante directa, pero supongo que cualquier cosa sencilla termina un patrón :) – Uri

2

Para mí, parece que 'thisCharacter' podría tener un objeto de 'inteligencia' para manejar la inteligencia detrás de escena, pero me pregunto si debería ser público en absoluto. Debería exponer esteCaracter.applyInt y thisCharacter.getInt en lugar del objeto que trata con él. No expongas tu implementación así.

2

Mantenga sus campos privados! Nunca querrás exponer mucho de tu API. Siempre puede hacer que algo sea público privado, pero no al revés, en versiones futuras.

Piensa como si lo convertirías en el próximo MMORPG. Tendría mucho margen para errores, errores y mal innecesario. Asegúrese de que las propiedades inmutables sean definitivas.

Piense en un reproductor de DVD, con su interfaz miniamilística (reproducir, detener, menú), y sin embargo, tal tecnicismo en el interior. Querrá ocultar todo lo que no sea vital en su programa.

1

Parece que su principal queja no es tanto con la abstracción de los métodos setter/getter, sino con la sintaxis del lenguaje para usarlos. Es decir, preferiría algo así como las propiedades de estilo C#.

Si este es el caso, entonces el lenguaje Java tiene relativamente poco que ofrecerle. El acceso directo al campo está bien, hasta que necesites cambiar a un getter o setter, y luego tendrás que hacer algunas refactorizaciones (posiblemente bien, si controlas toda la base de código).

Por supuesto, si la plataforma Java es un requisito, pero el idioma no lo es, existen otras alternativas. Scala tiene una muy buena sintaxis de propiedad, por ejemplo, junto con muchas otras características que podrían ser útiles para dicho proyecto. Y lo mejor de todo, se ejecuta en JVM, por lo que aún obtiene la misma portabilidad que obtendría al escribirla en el lenguaje Java.:)

4

Steve Yegge tenía una publicación de blog muy interesante (aunque larga) que cubría estos problemas: The Universal Design Pattern.

+0

¡Buena llamada! Ese es un gran artículo en defensa del patrón de "propiedades". – erickson

1

Lo que parece que tiene aquí es una capa única de un modelo compuesto.

Es posible que desee agregar métodos que agreguen abstracciones al modelo, en lugar de solo tenerlo como un conjunto de modelos de nivel inferior.

Los campos deben ser definitivos, por lo tanto, incluso si los hizo públicos, no pudo asignarles accidentalmente null.

El prefijo "get" está presente para todos los getters, por lo que probablemente sea más el aspecto inicial que un nuevo problema como tal.

0

Además de la respuesta de Uri (que apoyo totalmente), me gustaría sugerir que considere definir el mapa de atributos en los datos. Esto hará que su programa sea EXTREMADAMENTE flexible y tendrá en cuenta una gran cantidad de código que ni siquiera se da cuenta de que no necesita. Por ejemplo, un atributo puede saber a qué campo se une en la pantalla, a qué campo se une en la base de datos, una lista de acciones para ejecutar cuando el atributo cambia (un porcentaje recalculado% podría aplicarse tanto a la fuerza como a la acción). dex ...)

Haciéndolo de esta manera, no tiene código por atributo para escribir una clase en el DB o mostrarla en la pantalla. Simplemente itere sobre los atributos y use la información almacenada en el interior.

Lo mismo se puede aplicar a las habilidades: de hecho, los atributos y habilidades probablemente derivarían de la misma clase base.

Después de pasar por este camino, es probable que se encuentra con un archivo de texto bastante serio para definir atributos y habilidades, pero la adición de una nueva habilidad sería tan 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 

En algún momento , agregar una habilidad como esta no requeriría ningún cambio de código, todo podría hacerse en datos con bastante facilidad, y TODOS los datos se almacenan en un solo objeto de habilidad asociado a una clase. Se podría, por supuesto, definir las acciones de la misma manera:

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 

Aunque este ejemplo es bastante avanzada, usted debería ser capaz de visualizar la forma en que se llevaría a cabo sin ningún tipo de código. Imagine lo difícil que sería hacer algo así sin código si sus "atributos/habilidades" fueran en realidad variables miembro.

18

Parece que estás pensando en términos de if(player.dexterity > monster.dexterity) attacker = player. Necesitas pensar más como if(player.quickerThan(monster)) monster.suffersAttackFrom(player.getCurrentWeapon()). No juegues con estadísticas simples, expresa tu intención real y luego diseña tus clases alrededor de lo que se supone que deben hacer. Las estadísticas son un colapso de todos modos; lo que realmente te importa es si un jugador puede o no hacer alguna acción, o su capacidad/capacidad versus alguna referencia. Piense en términos de "es el personaje del jugador lo suficientemente fuerte" (player.canOpen(trapDoor)) en lugar de "¿tiene el personaje al menos 50 puntos de fuerza?".

+2

¡Buen punto! Piense más allá de una simple transcripción de manuales de D & D. – erickson

+0

Si bien traes un punto excelente, me temo que es justo reconsiderar el de Uri como la "respuesta" del hilo. :) – badp

1

¿Son las ventajas de la encapsulación tan masiva que debería seguir adelante y tener get get getters y setter getters y demás ... o debería tomar encapsulación en medidas saludables y dejar el campo como un campo público?

IMO, la encapsulación no tiene nada que ver con envolver un getter/setter alrededor de un campo privado. En pequeñas dosis, o al escribir bibliotecas de propósito general, la compensación es aceptable. Pero cuando se deja sin marcar en un sistema como el que está describiendo, es un antipattern.

El problema con getters/setters es que crean un acoplamiento excesivamente estrecho entre el objeto y esos métodos y el resto del sistema.

Una de las ventajas de la encapsulación real es que reduce la necesidad de getters y setters, desacoplando su objeto del resto del sistema en el proceso.

En lugar de exponer la implementación de GameCharacter con setIntelligence, ¿por qué no darle a GameCharacter una interfaz que refleje mejor su papel en el sistema de juego?

Por ejemplo, en lugar 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 qué no probar esto ?:

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

    public int getIntelligence() 
    { 
    // TODO 
    } 
} 

o incluso mejor:

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

En otras palabras, un GameCharacter puede agarrar GameObjects . El efecto de cada GameCharacter que agarra el mismo GameObject puede (y debe) variar, pero los detalles están completamente encapsulados dentro de cada implementación de GameCharacter.

Observe cómo el GameCharacter está ahora a cargo de manejar su propia actualización de inteligencia (comprobación de rango, etc.), que puede suceder al tomar GameObjects, por ejemplo. El colocador (y las complicaciones que nota al tenerlo) han desaparecido. Es posible que pueda prescindir por completo del método getIntelligence, según la situación. Allen Holub lleva esta idea a su logical conclusion, pero ese enfoque no parece ser muy común.

+0

¡Eso es exactamente lo que quería evitar! getIntelligence() setIntelligence() applyIntelligenceBonus() resetIntelligenceBonus() getStrength() setStrength() applyStrengthBonus() resetStrengthBonus() getWisdom() setWisdom() giveWisdomBonus() resetWisdomBonus() getDexterity() setDexterity() applyDexterityBonus() zeroDexterityBonus() getHP() setHP() dealDamage() healDamag e() ... al final tener una gran cantidad de código redundante haciendo la misma operación en diferentes campos. Tal vez ni siquiera son nombrados consistentemente. Esa es la definición de propenso a errores, en mi humilde opinión. :) – badp

0

Considere utilizar un marco de "modelado", como el EMF de Eclipse. Esto implica definir sus objetos usando algo como el editor Eclipse EMF, o en XML simple, o en Rational Rose. No es tan difícil como parece. Define atributos, restricciones, etc. Luego, el marco genera el código para usted. Agrega etiquetas @generated a las partes que agregó, como getter and setter methods. Puede personalizar el código y luego editar ya sea manualmente o a través de alguna GUI, y regenerar los archivos java.

Cuestiones relacionadas