Existem diferentes tipos de polimorfismo, o de interesse é geralmente polimorfismo em tempo de execução / despacho dinâmico.
Uma descrição de alto nível do polimorfismo de tempo de execução é que uma chamada de método faz coisas diferentes, dependendo do tipo de tempo de execução de seus argumentos: o próprio objeto é responsável por resolver uma chamada de método. Isso permite uma enorme quantidade de flexibilidade.
Uma das maneiras mais comuns de usar essa flexibilidade é a injeção de dependência , por exemplo, para que eu possa alternar entre implementações diferentes ou injetar objetos simulados para teste. Se eu souber com antecedência que haverá apenas um número limitado de opções possíveis, eu poderia tentar codificá-las com condicionais, por exemplo:
void foo() {
if (isTesting) {
... // do mock stuff
} else {
... // do normal stuff
}
}
Isso dificulta o código. A alternativa é introduzir uma interface para essa operação foo e escrever uma implementação normal e uma implementação simulada dessa interface e "injetar" na implementação desejada em tempo de execução. "Injeção de dependência" é um termo complicado para "transmitir o objeto correto como argumento".
Como exemplo do mundo real, atualmente estou trabalhando em um tipo de problema de aprendizado de máquina. Eu tenho um algoritmo que requer um modelo de previsão. Mas quero experimentar diferentes algoritmos de aprendizado de máquina. Então eu defini uma interface. O que eu preciso do meu modelo de previsão? Dada uma amostra de entrada, a previsão e seus erros:
interface Model {
def predict(sample) -> (prediction: float, std: float);
}
Meu algoritmo usa uma função de fábrica que treina um modelo:
def my_algorithm(..., train_model: (observations) -> Model, ...) {
...
Model model = train_model(observations);
...
y, std = model.predict(x)
...
}
Agora tenho várias implementações da interface do modelo e posso compará-las umas com as outras. Uma dessas implementações, na verdade, pega dois outros modelos e os combina em um modelo aprimorado. Então, graças a esta interface:
- meu algoritmo não precisa saber sobre modelos específicos com antecedência,
- Eu posso trocar facilmente modelos e
- Tenho muita flexibilidade na implementação de meus modelos.
Um caso de uso clássico de polimorfismo está nas GUIs. Em uma estrutura de GUI como Java AWT / Swing /…, existem componentes diferentes . A interface do componente / classe base descreve ações como pintar a própria tela ou reagir a cliques do mouse. Muitos componentes são contêineres que gerenciam subcomponentes. Como esse recipiente pode se desenhar?
void paint(Graphics g) {
super.paint(g);
for (Component child : this.subComponents)
child.paint(g);
}
Aqui, o contêiner não precisa saber sobre os tipos exatos dos subcomponentes com antecedência - desde que estejam em conformidade com a Component
interface que o contêiner pode simplesmente chamar de paint()
método polimórfico . Isso me dá a liberdade de estender a hierarquia de classes AWT com novos componentes arbitrários.
Existem muitos problemas recorrentes ao longo do desenvolvimento de software que podem ser resolvidos aplicando o polimorfismo como uma técnica. Esses pares problema-solução recorrentes são chamados de padrões de design e alguns deles são coletados no livro de mesmo nome. Nos termos desse livro, meu modelo de aprendizado de máquina injetado seria uma estratégia que eu uso para “definir uma família de algoritmos, encapsular cada um e torná-los intercambiáveis”. O exemplo Java-AWT em que um componente pode conter subcomponentes é um exemplo de composto .
Mas nem todo projeto precisa usar polimorfismo (além de permitir a injeção de dependência para testes de unidade, que é realmente um bom caso de uso). Caso contrário, a maioria dos problemas é muito estática. Como conseqüência, classes e métodos geralmente não são usados para polimorfismo, mas simplesmente como espaços de nomes convenientes e para o método bonito chamar sintaxe. Por exemplo, muitos desenvolvedores preferem chamadas de método como account.getBalance()
sobre uma chamada de função amplamente equivalente Account_getBalance(account)
. Essa é uma abordagem perfeitamente correta, mas muitas chamadas de "método" não têm nada a ver com polimorfismo.