
O que é isso e por quê?
Ao projetar, um desenvolvedor pode encontrar um problema: criaturas e objetos podem ter habilidades diferentes em diferentes combinações. Os sapos pulam e nadam, os patos nadam e voam, mas não com um peso, e os sapos podem voar com galhos e patos. Portanto, é conveniente mudar de herança para composição e adicionar habilidades dinamicamente. A necessidade de animar sapos voadores levou a uma rejeição injustificada dos métodos de habilidade e a transferência de seu código para as equipes em uma das implementações. Aqui está:
class CastSpellCommand extends Command { constructor (source, target, spell) { this.source = source; this.target = target; this.spell = spell; } execute () { const spellAbility = this.source.getAbility(SpellCastAbility);
O que pode ser feito?
Considere várias abordagens de natureza diferente:
Observador
class Executor extends Observer {} class Animator extends Observer {}
Uma solução clássica bem conhecida dos programadores. Você só precisa alterá-lo minimamente para verificar os valores retornados pelos observadores:
this.listeners.reduce((result, listener) => result && listener(action), true)
Desvantagem: os observadores devem assinar os eventos na ordem correta.
Se você manipular erros, o animador também poderá mostrar animações de ações com falha. Você pode passar o valor anterior para os observadores; conceitualmente, a solução permanece a mesma. Se métodos de observação ou funções de retorno de chamada são chamados, se um loop regular é usado em vez de convolução, os detalhes não são tão significativos.
Deixe como está
E de fato. A abordagem atual tem desvantagens e vantagens:
- Testar a capacidade de executar um comando requer a execução de um comando
- Argumentos em uma ordem de mudança, condições, prefixos de método são conectados
- Dependências de loop (comando <ortografia <comando)
- Entidades adicionais para cada ação (o método é substituído pelo método, classe e seu construtor)
- Excesso de conhecimento e ações de uma equipe individual: da mecânica do jogo aos erros de sincronização e manipulação direta das propriedades de outras pessoas
- A interface é enganosa (executa não apenas chamadas, mas também adiciona comandos via addChildren; o que, obviamente, faz o oposto)
- Necessidade duvidosa e implementação de instruções recursivas per se
- A classe despachante, se houver, não executa suas funções
- [+] Alegadamente, a única maneira de animar na prática, se as animações precisarem de dados completos (indicados como o principal motivo)
- [+] Provavelmente outros motivos
Algumas das deficiências podem ser tratadas separadamente, mas o restante exige mudanças mais drásticas.
ad hoc
- As condições para a execução da equipe, especialmente a mecânica do jogo, devem ser retiradas das equipes e executadas separadamente. As condições podem mudar no tempo de execução, e destacar os botões inativos em cinza ocorre na prática muito antes do início do trabalho de animação, sem mencionar a lógica. Para evitar a cópia, pode fazer sentido armazenar condições gerais em protótipos de capacidade.
- Métodos de retorno, em combinação com o parágrafo anterior, a necessidade de tais verificações desaparecerá:
const spellAbility = this.source.getAbility(SpellCastAbility);
O próprio mecanismo javascript mostrará o TypeError correto quando o método for chamado por engano. - A equipe também não precisa desse conhecimento:
healthAbility.health = Math.max( 0, resultHealth );
- Para resolver o problema dos argumentos que mudam de lugar, eles podem ser passados pelo objeto.
- Embora o código de chamada não esteja disponível para estudo, parece que a maioria das deficiências cresce devido à maneira não ideal de invocar ações do jogo. Por exemplo, os manipuladores de botão acessam entidades específicas. Portanto, substituí-los em manipuladores por comandos específicos parece bastante natural. Se você tem um expedidor, é muito mais fácil chamar uma animação após a ação, você pode transferir as mesmas informações para ela, para que não haja falta de dados.
Fila
Para mostrar a animação da ação após a conclusão da ação, basta adicioná-los à fila e executá-los aproximadamente como na solução 1.
[ [ walkRequirements, walkAction, walkAnimation ], [ castRequirements, castAction, castAnimation ],
Não importa quais entidades estão na matriz: funções banidas com os parâmetros necessários, instâncias de classes personalizadas ou objetos comuns.
O valor dessa solução é a simplicidade e a transparência; é fácil criar uma janela deslizante para visualizar os N últimos comandos.
Adequado para prototipagem e depuração.
Classe de subestudo
Nós fazemos uma aula de animação para a habilidade.
class MovementAbility { walk (...args) {
Se for impossível fazer alterações na classe de chamada, herdamos dela ou decoramos o método desejado para que ele chame a animação. Ou transmitimos animação em vez de habilidade, eles têm a mesma interface.
Bem adequados quando você precisa praticamente do mesmo conjunto de métodos, eles podem ser verificados e testados automaticamente.
Combinações de métodos
const AnimatedMovementAbility = combinedClass(MovementAbility, { ['*:before'] (method, ...args) {
Seria uma oportunidade interessante com suporte ao idioma nativo.
É bom usá-lo se essa opção for mais produtiva, embora um proxy seja realmente necessário.
Proxies
Nós envolvemos habilidades em proxies, capturamos métodos em getters.
new Proxy(new MovementAbility, {})
Desvantagem: muitas vezes mais lenta que as chamadas regulares, o que não é tão importante para a animação. Em um servidor que processa milhões de objetos, a desaceleração seria perceptível, mas o servidor não precisa de animação.
Promessa
Você pode construir cadeias a partir do Promise, mas há outra opção (ES2018):
for await (const action of actionDispatcher.getActions()) {
getActions retorna um iterador de ação assíncrono. O próximo método do iterador retorna a promessa adiada da próxima ação. Depois de processar eventos do usuário e do servidor, chamamos resolve (), cria uma nova promessa.
Melhor equipe
Crie objetos como este:
{actor, ability, method, options}
O código se resume a verificar e chamar o método de capacidade com parâmetros. A opção mais fácil e produtiva.
Nota