
¿Qué es esto y por qué?
Al diseñar, un desarrollador puede encontrar un problema: las criaturas y los objetos pueden tener diferentes habilidades en diferentes combinaciones. Las ranas saltan y nadan, los patos nadan y vuelan, pero no con un peso, y las ranas pueden volar con una rama y patos. Por lo tanto, es conveniente cambiar de herencia a composición y agregar habilidades dinámicamente. La necesidad de animar a las ranas voladoras condujo a un rechazo injustificado de los métodos de habilidad y la eliminación de su código en los equipos en una de las implementaciones. Aquí esta:
class CastSpellCommand extends Command { constructor (source, target, spell) { this.source = source; this.target = target; this.spell = spell; } execute () { const spellAbility = this.source.getAbility(SpellCastAbility);
Que se puede hacer
Considere varios enfoques de diferente naturaleza:
Observador
class Executor extends Observer {} class Animator extends Observer {}
Una solución clásica bien conocida por los programadores. Solo necesita cambiarlo mínimamente para verificar los valores devueltos por los observadores:
this.listeners.reduce((result, listener) => result && listener(action), true)
Desventaja: los observadores deben suscribirse a los eventos en el orden correcto.
Si maneja errores, el animador también podrá mostrar animaciones de acciones fallidas. Puede pasar el valor anterior a los observadores; conceptualmente, la solución sigue siendo la misma. Ya sea que se llamen métodos de observación o funciones de devolución de llamada, si se usa un bucle regular en lugar de convolución, los detalles no son tan significativos.
Dejar como está
Y de hecho. El enfoque actual tiene desventajas y ventajas:
- Probar la capacidad de ejecutar un comando requiere un comando
- Los argumentos en un orden cambiante, las condiciones y los prefijos de método están conectados
- Dependencias de bucle (comando <hechizo <comando)
- Entidades adicionales para cada acción (el método se reemplaza por el método, la clase y su constructor)
- Conocimiento y acciones excesivas de un equipo individual: desde la mecánica del juego hasta los errores de sincronización y la manipulación directa de las propiedades de otras personas.
- La interfaz es engañosa (ejecuta no solo llamadas, sino que también agrega comandos a través de addChildren; que, obviamente, hace lo contrario)
- Necesidad dudosa e implementación de instrucciones recursivas per se
- La clase de despachador, si la hay, no realiza sus funciones.
- [+] Supuestamente la única forma de animar en la práctica, si las animaciones necesitan datos completos (indicados como la razón principal)
- [+] Probablemente otras razones
Algunas de las deficiencias pueden tratarse por separado, pero el resto requiere cambios más drásticos.
ad hoc
- Las condiciones para la ejecución del equipo, especialmente la mecánica del juego, deben eliminarse de los equipos y ejecutarse por separado. Las condiciones pueden cambiar en tiempo de ejecución, y resaltar botones inactivos en gris ocurre en la práctica mucho antes de que comience el trabajo de animación, sin mencionar la lógica. Para evitar copiar, podría tener sentido almacenar condiciones generales en prototipos de habilidades.
- Métodos de devolución, en combinación con el párrafo anterior, la necesidad de tales comprobaciones desaparecerá:
const spellAbility = this.source.getAbility(SpellCastAbility);
El motor de JavaScript en sí mostrará el error de tipo correcto cuando se llama por error al método. - El equipo tampoco necesita ese conocimiento:
healthAbility.health = Math.max( 0, resultHealth );
- Para resolver el problema de los argumentos que cambian de lugar, el objeto puede pasarlos.
- Aunque el código de llamada no está disponible para estudio, parece que la mayoría de las deficiencias crecen debido a la forma no óptima de invocar acciones del juego. Por ejemplo, los manejadores de botones acceden a entidades específicas. Por lo tanto, reemplazarlos en controladores con comandos específicos parece bastante natural. Si tiene un despachador, es mucho más fácil llamar a una animación después de la acción, puede transferirle la misma información para que no falten datos.
Cola
Para mostrar la animación de la acción después de que se complete la acción, es suficiente agregarlos a la cola y ejecutarlos aproximadamente como en la solución 1.
[ [ walkRequirements, walkAction, walkAnimation ], [ castRequirements, castAction, castAnimation ],
No importa qué entidades estén en la matriz: funciones prohibidas con los parámetros necesarios, instancias de clases personalizadas u objetos ordinarios.
El valor de tal solución es la simplicidad y la transparencia; es fácil crear una ventana deslizante para ver los últimos N comandos.
Muy adecuado para la creación de prototipos y depuración.
Clase de suplente
Hacemos una clase de animación para la habilidad.
class MovementAbility { walk (...args) {
Si es imposible hacer cambios en la clase de llamada, heredamos de ella o decoramos el método deseado para que llame a la animación. O transmitimos animación en lugar de habilidad, tienen la misma interfaz.
Muy adecuados cuando necesita prácticamente el mismo conjunto de métodos, pueden verificarse y probarse automáticamente.
Método de combinaciones
const AnimatedMovementAbility = combinedClass(MovementAbility, { ['*:before'] (method, ...args) {
Sería una oportunidad interesante con soporte de idioma nativo.
Es bueno usarlo si esta opción es más productiva, aunque en realidad se necesita un proxy.
Proxies
Envolvemos habilidades en proxies, atrapamos métodos en getters.
new Proxy(new MovementAbility, {})
Desventaja: muchas veces más lenta que las llamadas regulares, lo que no es tan importante para la animación. En un servidor que procesa millones de objetos, la desaceleración sería notable, pero el servidor no necesita animación.
Promesa
Puede construir cadenas desde Promise, pero hay otra opción (ES2018):
for await (const action of actionDispatcher.getActions()) {
getActions devuelve un iterador de acción asincrónico. El siguiente método del iterador devuelve la Promesa diferida de la siguiente acción. Después de procesar eventos del usuario y el servidor, llamamos a resolve (), crea una nueva promesa.
Mejor equipo
Crea objetos como este:
{actor, ability, method, options}
El código se reduce a verificar y llamar al método de habilidad con parámetros. La opción más fácil y productiva.
Nota