Como o polimorfismo é usado no mundo real? [fechadas]


17

Estou tentando entender como o polimorfismo é usado em um projeto da vida real, mas só consigo encontrar o exemplo clássico (ou algo semelhante) de ter uma Animalclasse pai com um método speak()e muitas classes filho que substituem esse método e agora você pode chamar o método speak()em qualquer um dos objetos filhos, por exemplo:

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();



1
As coleções, que você vê e usa todos os dias, são suficientes para entender o que é polimorfismo. Mas como usar o polimorfismo efetivamente na solução de problemas é uma habilidade que você ganha mais por experiência e não apenas por discutir. Vá em frente e suja as mãos.
Durgadass S

Se você possui um conjunto de tipos que suportam algum tipo de interface mínima (por exemplo, um conjunto de objetos que precisam ser desenhados), uma interface geralmente serve para ocultar as diferenças entre os objetos da chamada para desenhá-la. Além disso, se você estiver criando (ou trabalhando com) uma API que possui métodos que podem atender a um objeto base e um número significativo de tipos que herdam dele mais ou menos da mesma maneira , o polimorfismo pode ser a melhor maneira de abstrair as diferenças entre esses tipos.
Jrh 22/07

Em geral, se você está frequentemente criando métodos sobrecarregados para lidar com tipos diferentes e o código é semelhante, ou se você está escrevendo if(x is SomeType) DoSomething()frequentemente, pode valer a pena usar polimorfismo. Para mim, o polimorfismo é uma decisão semelhante a quando fazer um método separado. Se descobri que repeti o código algumas vezes, costumo refatorá-lo em um método e, se achar que estou criando if object is this type do thiscódigo com frequência, talvez seja vale refatorar e adicionar uma interface ou classe.
Jrh 22/07

Respostas:


35

Stream é um ótimo exemplo de polimorfismo.

Stream representa uma "sequência de bytes que pode ser lida ou gravada". Mas essa sequência pode vir de arquivo, memória ou muitos tipos de conexões de rede. Ou pode servir como decorador, que envolve o fluxo existente e transforma os bytes de alguma forma, como criptografia ou compactação.

Dessa forma, o cliente que usa o Stream não precisa se preocupar com a origem dos bytes. Só que eles podem ser lidos em sequência.

Alguns diriam que Streamé um exemplo errado de polimorfismo, porque define muitos "recursos" que seus implementadores não suportam, como o fluxo de rede que permite apenas leitura ou gravação, mas não os dois ao mesmo tempo. Ou falta de busca. Mas isso é apenas uma questão de complexidade, pois Streampode ser subdividido em várias partes que podem ser implementadas independentemente.


2
Em linguagens com herança múltipla e virtual como C ++, este exemplo pode até demonstrar o padrão "diamante temido" ... derivando classes de fluxo de entrada e saída de uma classe de fluxo base e estendendo ambas para criar um fluxo de E / S
gyre

2
@gyre E bem feito, não há razão para "temer" o padrão de diamante. É importante estar ciente da contraparte oposta no diamante e não causar conflitos de nome com ele, e é um desafio, e irritante, e uma razão para evitar o padrão de diamante onde for possível ... mas não exagere quando simplesmente ter, por exemplo, uma convenção de nomenclatura pode resolver os problemas.
KRyan

Os +1 Streamsão o meu exemplo de polimorfismo favorito de todos os tempos. Nem sequer tento ensinar às pessoas o modelo defeituoso de 'animal, mamífero, cachorro', Streammas fazemos um trabalho melhor.
Pharap

@KRyan Eu não estava expressando meus próprios pensamentos, chamando-o de "diamante temido", acabei de ouvi-lo referido como tal. Eu concordo completamente; Eu acho que é algo que todo desenvolvedor deve ser capaz de entender e usar adequadamente.
Gyre

@gyre Oh, sim, eu realmente entendi; foi por isso que comecei com "e" para indicar que era uma extensão do seu pensamento, e não uma contradição.
KRyan

7

Um exemplo típico relacionado a jogos seria uma classe base Entity, fornecendo membros comuns como draw()ou update().

Para um exemplo orientado a dados mais puro, poderia haver uma classe base Serializablefornecendo um saveToStream()e loadFromStream().


6

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 Componentinterface 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.


6

Você vê muita herança e polimorfismo na maioria dos kits de ferramentas da interface do usuário.

Por exemplo, no kit de ferramentas da interface do usuário JavaFX, Buttonherda da ButtonBasequal herda da Labeledqual herda da Controlqual herda da Regionqual herda da Parentqual herda da Nodequal herda Object. Muitas camadas substituem alguns métodos dos anteriores.

Quando você deseja que esse botão apareça na tela, adicione-o a a Pane, que pode aceitar qualquer coisa herdada Nodequando criança. Mas como um Painel sabe o que fazer com um Botão quando o vê apenas como um objeto Nó genérico? Esse objeto pode ser qualquer coisa. O painel pode fazer isso porque o Button redefine os métodos do Node com qualquer lógica específica do botão. O painel chama os métodos definidos no Node e deixa o restante para o próprio objeto. Este é um exemplo perfeito de polimorfismo aplicado.

Os kits de ferramentas da interface do usuário têm um significado muito alto no mundo real, tornando-os úteis para ensinar por razões acadêmicas e práticas.

No entanto, os kits de ferramentas da interface do usuário também têm uma desvantagem significativa: eles tendem a ser enormes . Quando um engenheiro de software neófito tenta entender o funcionamento interno de uma estrutura de interface do usuário comum, geralmente encontra mais de cem classes , a maioria delas servindo a propósitos muito esotéricos. "O que diabos é isso ReadOnlyJavaBeanLongPropertyBuilder? É importante? Eu tenho que entender para que serve?" Iniciantes podem facilmente se perder nessa toca de coelho. Portanto, eles podem fugir aterrorizados ou permanecer na superfície onde aprendem a sintaxe e tentam não pensar muito sobre o que realmente está acontecendo sob o capô.


3

Embora já existam bons exemplos aqui, outro é substituir animais por dispositivos:

  • Devicepode ser powerOn(), powerOff(), setSleep()e lata getSerialNumber().
  • SensorDevicepode fazer tudo isso, e fornecer funções polimórficas, como getMeasuredDimension(), getMeasure(), alertAt(threashhold)e autoTest().
  • obviamente, getMeasure()não será implementado da mesma maneira para um sensor de temperatura, um detector de luz, um detector de som ou um sensor volumétrico. E, é claro, cada um desses sensores mais especializados pode ter algumas funções adicionais disponíveis.

2

A apresentação é um aplicativo muito comum, talvez o mais comum seja ToString (). Que é basicamente Animal.Speak (): você diz para um objeto se manifestar.

De um modo mais geral, você diz a um objeto para "fazer as coisas". Pense em Salvar, Carregar, Inicializar, Dispor, ProcessData, GetStatus.


2

Meu primeiro uso prático do polimorfismo foi uma implementação do Heap em java.

Eu tinha classe base com implementação de métodos insert, removeTop onde a diferença entre max e min Heap seria apenas como o método comparar funciona.

abstract class Heap {  

 abstract boolean compare ( int x , int y );

 boolean insert(int x ) { ... }

 int removeTop() { ... }
}

Então, quando eu queria ter MaxHeap e MinHeap, eu poderia apenas usar herança.

class MaxHeap extends Heap {

   MaxHeap(int maxSize) {super(maxSize);}

   @Override
   boolean compare(int x, int y) {
       return x>y; // x<y for minHeap
   }
}

1

Aqui está um cenário da vida real para o polimorfismo da tabela de aplicativos da web / banco de dados :

Uso o Ruby on Rails para desenvolver aplicativos da Web, e uma coisa que muitos dos meus projetos têm em comum é a capacidade de fazer upload de arquivos (fotos, PDFs etc.). Por exemplo, um Userpode ter várias fotos de perfil e um Producttambém pode ter muitas imagens de produtos. Ambos têm o comportamento de carregar e armazenar imagens, além de redimensionar, gerar miniaturas etc. Para permanecer seco e compartilhar o comportamento Picture, queremos tornar Picturepolimórficos para que possam pertencer a ambos Usere Product.

No Rails, eu projetava meus modelos da seguinte forma:

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class User < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end

e uma migração de banco de dados para criar a picturestabela:

class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end

    add_index :pictures, [:imageable_type, :imageable_id]
  end
end

As colunas imageable_ide imageable_typesão usadas internamente pelo Rails. Basicamente, imageable_typecontém o nome da classe ( "User", "Product", etc.), e imageable_idé o id do registro associado. Então, imageable_type = "User"e imageable_id = 1seria o registro na userstabela com id = 1.

Isso nos permite fazer coisas como user.picturesacessar as fotos do usuário e product.picturesobter as fotos de um produto. Então, todo o comportamento relacionado à imagem é encapsulado na Photoclasse (e não uma classe separada para cada modelo que precisa de fotos), para que as coisas sejam mantidas SECA.

Mais leitura: Associações polimórficas do Rails .


0

Existem muitos algoritmos de classificação disponíveis, como classificação de bolhas, classificação de inserção, classificação rápida, classificação de heap, etc.

O cliente fornecido com a interface de classificação preocupa-se apenas em fornecer a matriz como uma entrada e, em seguida, recebe a matriz classificada. Durante o tempo de execução, dependendo de certos fatores, a implementação apropriada da classificação pode ser usada. Este é um exemplo do mundo real de onde o polimorfismo é usado.

O que eu descrevi acima é um exemplo de polimorfismo em tempo de execução, enquanto a sobrecarga de método é um exemplo de polimorfismo em tempo de compilação, quando mais completas, dependendo dos tipos de parâmetros i / pe / o / p e o número de parâmetros vincula o chamador ao método correto no próprio tempo de conformidade.

Espero que isso esclareça.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.