Sue está projetando uma biblioteca JavaScript Magician.js
,. Seu pino de linch é uma função que retira Rabbit
o argumento passado.
Ela sabe que seus usuários podem querer tirar um coelho de um String
, um Number
, um Function
, talvez até um HTMLElement
. Com isso em mente, ela poderia criar sua API da seguinte maneira:
A interface estrita
Magician.pullRabbitOutOfString = function(str) //...
Magician.pullRabbitOutOfHTMLElement = function(htmlEl) //...
Cada função no exemplo acima saberia como lidar com o argumento do tipo especificado no nome da função / nome do parâmetro.
Ou ela poderia projetá-lo assim:
A interface "ad hoc"
Magician.pullRabbit = function(anything) //...
pullRabbit
precisaria considerar a variedade de tipos esperados diferentes que o anything
argumento poderia ser, bem como (é claro) um tipo inesperado:
Magician.pullRabbit = function(anything) {
if (anything === undefined) {
return new Rabbit(); // out of thin air
} else if (isString(anything)) {
// more
} else if (isNumber(anything)) {
// more
}
// etc.
};
O primeiro (estrito) parece mais explícito, talvez mais seguro e talvez com melhor desempenho - pois há pouca ou nenhuma sobrecarga para verificação ou conversão de tipo. Mas o último (ad hoc) parece mais simples olhando de fora, na medida em que "simplesmente funciona" com qualquer argumento que o consumidor da API achar conveniente passar para ele.
Para a resposta a essa pergunta , eu gostaria de ver prós e contras específicos de qualquer uma das abordagens (ou de uma abordagem diferente, se nenhuma delas é ideal), para que Sue saiba qual abordagem deve ser adotada ao criar a API de sua biblioteca.
fonte
Respostas:
Alguns prós e contras
Prós para polimórficos:
Prós para ad-hoc:
Cat
instância? Isso apenas funcionaria? se não, qual é o comportamento? Se eu não limitar o tipo aqui, tenho que fazê-lo na documentação ou nos testes que podem fazer um contrato pior.pullRabbitOutOfString
versão provavelmente será muito mais rápida em motores como o V8. Veja este vídeo para mais informações. Edit: Eu mesmo escrevi um perfume, acontece que, na prática, esse nem sempre é o caso .Algumas soluções alternativas:
Na minha opinião, esse tipo de design não é muito 'Java-Scripty' para começar. JavaScript é uma linguagem diferente, com idiomas diferentes de linguagens como C #, Java ou Python. Esses idiomas se originam em anos de desenvolvedores tentando entender as partes fracas e fortes da linguagem, o que eu faria é tentar ficar com esses idiomas.
Existem duas soluções legais que posso pensar:
Solução 1: elevando objetos
Uma solução comum para esse problema é "elevar" objetos com a capacidade de obter coelhos deles.
Ou seja, tem uma função que pega algum tipo de objeto e acrescenta puxar um chapéu para ele. Algo como:
Posso criar essas
makePullable
funções para outros objetos, criar ummakePullableString
etc. Estou definindo a conversão em cada tipo. No entanto, depois de elevar meus objetos, não tenho tipo de usá-los de maneira genérica. Uma interface em JavaScript é determinada por uma digitação de pato, se houver umpullOfHat
método, eu posso puxá-la com o método do Magician.Então Magician poderia fazer:
Elevar objetos, usar algum tipo de padrão mixin parece ser a coisa mais JS a fazer. (Observe que isso é problemático com os tipos de valor no idioma, que são string, número, nulo, indefinido e booleano, mas todos podem ser usados em caixas)
Aqui está um exemplo de como esse código pode parecer
Solução 2: Padrão de Estratégia
Ao discutir esta questão na sala de bate-papo JS no StackOverflow, meu amigo phenomnomnominal sugeriu o uso do padrão Strategy .
Isso permitiria adicionar as habilidades para extrair coelhos de vários objetos em tempo de execução e criaria muito código JavaScript. Um mágico pode aprender a tirar objetos de diferentes tipos de cartolas, e os puxa com base nesse conhecimento.
Aqui está como isso pode parecer no CoffeeScript:
Você pode ver o código JS equivalente aqui .
Dessa forma, você se beneficia dos dois mundos, a ação de como puxar não está fortemente acoplada aos objetos ou ao Mágico, e acho que isso cria uma solução muito agradável.
O uso seria algo como:
fonte
O problema é que você está tentando implementar um tipo de polimorfismo que não existe no JavaScript - o JavaScript é quase universalmente melhor tratado como uma linguagem tipada por pato, mesmo que ele suporte algumas faculdades de tipo.
Para criar a melhor API, a resposta é que você deve implementar as duas. É um pouco mais digitado, mas economizará muito trabalho a longo prazo para os usuários da sua API.
pullRabbit
deve ser apenas um método de árbitro que verifica os tipos e chama a função apropriada associada a esse tipo de objeto (por exemplopullRabbitOutOfHtmlElement
).Dessa forma, enquanto os usuários de prototipagem podem usar
pullRabbit
, mas se perceberem uma desaceleração, poderão implementar a verificação de tipo no final (provavelmente de maneira mais rápida) e apenas ligarpullRabbitOutOfHtmlElement
diretamente.fonte
Este é o JavaScript. À medida que você melhora, descobre que muitas vezes há um caminho intermediário que ajuda a negar dilemas como esse. Além disso, realmente não importa se um 'tipo' não suportado é capturado por algo ou se quebra quando alguém tenta usá-lo, porque não há compilação versus tempo de execução. Se você usar errado, ele quebra. Tentar esconder que ele quebrou ou fazê-lo funcionar a meio caminho quando quebrou não muda o fato de que algo está quebrado.
Portanto, coma seu bolo e coma-o também, e aprenda a evitar confusão de tipo e quebra desnecessária, mantendo tudo muito, muito óbvio, como bem nomeado e com todos os detalhes certos, nos lugares certos.
Primeiro de tudo, eu recomendo que você tenha o hábito de colocar seus patos em uma fila antes de precisar verificar os tipos. A coisa mais enxuta e eficiente (mas nem sempre melhor no que diz respeito aos construtores nativos) seria acertar os protótipos primeiro, para que seu método nem precise se preocupar com o tipo suportado em jogo.
Nota: É amplamente considerado como uma forma incorreta fazer isso com o Object, pois tudo herda do Object. Eu pessoalmente evitaria a função também. Alguns podem se sentir impacientes ao tocar no protótipo de qualquer construtor nativo, o que pode não ser uma política ruim, mas o exemplo ainda pode servir ao trabalhar com seus próprios construtores de objetos.
Eu não me preocuparia com essa abordagem para um método de uso específico que provavelmente não perturba algo de outra biblioteca em um aplicativo menos complicado, mas é um bom instinto evitar evitar algo excessivamente geral nos métodos nativos em JavaScript, se você não o fizer. a menos que você esteja normalizando métodos mais recentes em navegadores desatualizados.
Felizmente, você sempre pode pré-mapear tipos ou nomes de construtores para métodos (cuidado com o IE <= 8, que não possui <object> .constructor.name, exigindo que você os analise dos resultados de toString da propriedade construtor). Você ainda está checando o nome do construtor (typeof é meio inútil em JS ao comparar objetos), mas pelo menos parece muito melhor do que uma declaração de switch gigante ou encadeia se / else em todas as chamadas do método para o que poderia ser amplo variedade de objetos.
Ou usando a mesma abordagem de mapa, se você quiser acessar o componente 'this' dos diferentes tipos de objetos para usá-los como se fossem métodos sem tocar em seus protótipos herdados:
Um bom princípio geral em qualquer idioma da IMO é tentar resolver detalhes de ramificação como este antes de chegar ao código que realmente aciona o gatilho. Dessa forma, é fácil ver todos os jogadores envolvidos no nível superior da API para obter uma boa visão geral, mas também é muito mais fácil descobrir onde os detalhes com os quais alguém pode se importar provavelmente serão encontrados.
Nota: tudo isso não foi testado, porque presumo que ninguém realmente tenha um uso de RL para isso. Tenho certeza de que há erros de digitação / erros.
fonte
Esta (para mim) é uma pergunta interessante e complicada de responder. Na verdade, eu gosto dessa pergunta e farei o possível para responder. Se você pesquisar alguma coisa sobre os padrões da programação javascript, encontrará tantas maneiras "certas" de fazê-lo quantas pessoas estão divulgando a maneira "certa" de fazê-lo.
Mas como você está procurando uma opinião sobre qual caminho é melhor. Aqui vai nada.
Eu, pessoalmente, preferiria a abordagem de design "adhoc". Vindo de um background c ++ / C #, esse é mais o meu estilo de desenvolvimento. Você pode criar uma solicitação pullRabbit e fazer com que esse tipo de solicitação verifique o argumento passado e faça alguma coisa. Isso significa que você não precisa se preocupar com o tipo de argumento que está sendo passado a qualquer momento. Se você usar a abordagem estrita, ainda precisará verificar qual é o tipo da variável, mas faria isso antes de fazer a chamada do método. Portanto, no final, a questão é: você deseja verificar o tipo antes de fazer a ligação ou depois.
Espero que isso ajude, sinta-se à vontade para fazer mais perguntas em relação a esta resposta, farei o possível para esclarecer minha posição.
fonte
Quando você escreve, Magician.pullRabbitOutOfInt, ele documenta o que você pensou quando escreveu o método. O chamador espera que isso funcione se for passado um número inteiro. Quando você escreve Magician.pullRabbitOutOfAnything, o interlocutor não sabe o que pensar e precisa se aprofundar no seu código e experimentar. Pode funcionar para um Int, mas funcionará para um Long? Um flutuador? O dobro? Se você está escrevendo este código, até onde está disposto a ir? Que tipos de argumentos você deseja apoiar?
A ambiguidade leva tempo para entender. Não estou nem convencido de que seja mais rápido escrever:
Vs:
OK, então eu adicionei uma exceção ao seu código (o que eu recomendo) para informar ao chamador que você nunca imaginou que eles passariam o que fizeram. Mas escrever métodos específicos (não sei se o JavaScript permite fazer isso) não é mais código e é muito mais fácil entender como o chamador. Ele define suposições realistas sobre o que o autor deste código pensou e facilita o uso do código.
fonte