Despacho duplo

Da Wikipédia, a enciclopédia livre
Ir para a navegação Saltar para pesquisar

Na engenharia de software , o despacho duplo é uma forma especial de despacho múltiplo e um mecanismo que despacha uma chamada de função para diferentes funções concretas, dependendo dos tipos de tempo de execução de dois objetos envolvidos na chamada. Na maioria dos sistemas orientados a objetos , a função concreta que é chamada a partir de uma chamada de função no código depende do tipo dinâmico de um único objeto e, portanto, são conhecidas como chamadas de despacho único ou simplesmente chamadas de função virtual .

Dan Ingalls descreveu pela primeira vez como usar despacho duplo em Smalltalk , chamando-o de polimorfismo múltiplo . [1]

Visão geral

O problema geral abordado é como despachar uma mensagem para diferentes métodos dependendo não apenas do receptor, mas também dos argumentos.

Para isso, sistemas como o CLOS implementam despacho múltiplo . Despacho duplo é outra solução que reduz gradualmente o polimorfismo em sistemas que não suportam despacho múltiplo.

Casos de uso

O envio duplo é útil em situações em que a escolha da computação depende dos tipos de tempo de execução de seus argumentos. Por exemplo, um programador pode usar despacho duplo nas seguintes situações:

  • Classificando um conjunto misto de objetos: os algoritmos exigem que uma lista de objetos seja classificada em alguma ordem canônica. Decidir se um elemento vem antes de outro requer conhecimento de ambos os tipos e possivelmente algum subconjunto dos campos.
  • Os algoritmos de colisão adaptativa geralmente exigem que as colisões entre diferentes objetos sejam tratadas de maneiras diferentes. Um exemplo típico é em um ambiente de jogo onde a colisão entre uma nave espacial e um asteróide é calculada de forma diferente da colisão entre uma nave espacial e uma estação espacial. [2]
  • Algoritmos de pintura que exigem que os pontos de interseção de sprites sobrepostos sejam renderizados de uma maneira diferente.
  • Os sistemas de gestão de pessoal podem despachar diferentes tipos de trabalhos para diferentes pessoas. Um schedulealgoritmo que recebe um objeto de pessoa digitado como contador e um objeto de trabalho digitado como engenharia rejeita o agendamento dessa pessoa para esse trabalho.
  • Sistemas de manipulação de eventos que usam o tipo de evento e o tipo do objeto receptor para chamar a rotina de manipulação de eventos correta.
  • Sistemas de fechaduras e chaves onde existem muitos tipos de fechaduras e muitos tipos de chaves e cada tipo de chave abre vários tipos de fechaduras. Não apenas você precisa conhecer os tipos de objetos envolvidos, mas o subconjunto de "informações sobre uma determinada chave que são relevantes para ver se uma determinada chave abre uma determinada fechadura" é diferente entre os diferentes tipos de fechadura.

Um idioma comum

O idioma comum, como nos exemplos apresentados acima, é que a seleção do algoritmo apropriado é baseada nos tipos de argumento da chamada em tempo de execução. A chamada está, portanto, sujeita a todos os custos de desempenho adicionais usuais associados à resolução dinâmica de chamadas, geralmente mais do que em uma linguagem que suporta apenas despacho de método único. Em C++ , por exemplo, uma chamada de função dinâmica geralmente é resolvida por um único cálculo de deslocamento - o que é possível porque o compilador conhece a localização da função na tabela de métodos do objeto e, portanto, pode calcular o deslocamento estaticamente. Em uma linguagem com suporte duplodespacho, isso é um pouco mais caro, porque o compilador deve gerar código para calcular o deslocamento do método na tabela de métodos em tempo de execução, aumentando assim o comprimento geral do caminho da instrução (por um valor que provavelmente não será mais do que o número total de chamadas para a função, o que pode não ser muito significativo).

Exemplo em Ruby

Um caso de uso comum é exibir um objeto em uma porta de exibição que pode ser uma tela ou uma impressora, ou algo totalmente diferente que ainda não existe. Esta é uma implementação ingênua de como lidar com essas diferentes mídias.

class  Rectangle 
  def  display_on ( port ) 
    # seleciona o código correto com base na classe do objeto 
    case  port 
      quando  DisplayPort 
        # código para exibição em DisplayPort 
      quando  PrinterPort 
        # código para exibição em PrinterPort 
      quando  RemotePort 
        # código para exibição em RemotePort 
    end 
  end 
end

O mesmo precisaria ser escrito para Oval, Triangle e qualquer outro objeto que queira se exibir em uma mídia, e todos precisariam ser reescritos se um novo tipo de porta fosse criado. O problema é que existe mais de um grau de polimorfismo: um para despachar o método display_on para um objeto e outro para selecionar o código correto (ou método) para exibição.

Uma solução muito mais limpa e sustentável é então fazer um segundo despacho, desta vez para selecionar o método correto para exibir o objeto na mídia:

class  Rectangle 
  def  display_on ( port ) # segunda 
    porta de despacho 
    . display_rectangle ( self ) end end
  


class  Oval 
  def  display_on ( port ) # segunda 
    porta de despacho 
    . display_oval ( self ) end end
  


class  DisplayPort 
  def  display_rectangle ( objeto ) 
    # código para exibir um retângulo em um DisplayPort 
  end 
  def  display_oval ( objeto ) 
    # código para exibir um oval em um DisplayPort 
  end 
  # ... 
end

class  PrinterPort 
  def  display_rectangle ( objeto ) 
    # código para exibir um retângulo em um PrinterPort 
  end 
  def  display_oval ( objeto ) 
    # código para exibir um oval em um PrinterPort 
  end 
  # ... 
end

Despacho duplo em C++

À primeira vista, o despacho duplo parece ser um resultado natural da sobrecarga de funções . A sobrecarga de função permite que a função chamada dependa do tipo do argumento. A sobrecarga de função, no entanto, é feita em tempo de compilação usando " name mangling " onde o nome interno da função codifica o tipo do argumento. Por exemplo, uma função foo(int)pode ser chamada internamente de __foo_i e a função foo(double)pode ser chamada de __foo_d . Assim, não há colisão de nomes e nenhuma consulta de tabela virtual. Por outro lado, o despacho dinâmico é baseado no tipo do objeto de chamada, o que significa que ele usa funções virtuais (substituição) em vez de sobrecarga de função, e resulta em uma pesquisa vtable. Considere o seguinte exemplo, escrito em C++ , de colisões em um jogo:

classe  Nave Espacial {}; 
classe  ApolloSpacecraft : nave espacial pública {};    

classe  Asteróide { 
público :
  virtual void CollideWith ( SpaceShip & ) {   
    std :: cout << "Asteroide atingiu uma nave espacial \n " ;  
  }
  virtual void CollideWith ( ApolloSpacecraft & ) {   
    std :: cout << "Asteroide atingiu uma nave Apollo \n " ;  
  }
};

class  ExplodingAsteroid : public Asteroid {    
público :
  void CollideWith ( SpaceShip & ) override {   
    std :: cout << "ExplodingAsteroid atingiu uma nave espacial \n " ;  
  }
  void CollideWith ( ApolloSpacecraft & ) override {   
    std :: cout << "ExplodingAsteroid atingiu uma nave Apollo \n " ;  
  }
};

Se você tem:

Asteroid theAsteroid ; 
Nave Espacial a Nave Espacial ; 
ApolloSpacecraft theApolloSpacecraft ; 

então, por causa da sobrecarga de funções,

oAsteróide . CollideWith ( a Nave Espacial ); oAsteróide . CollideWith ( theApolloSpacecraft ); 

imprimirá, respectivamente, Asteroid hit a SpaceShipe Asteroid hit an ApolloSpacecraft, sem usar nenhum despacho dinâmico. Além disso:

ExplodingAsteroid theExplodingAsteroid ; 
theExplodingAsteroid . CollideWith ( a Nave Espacial ); theExplodingAsteroid . CollideWith ( theApolloSpacecraft ); 

imprimirá ExplodingAsteroid hit a SpaceShipe ExplodingAsteroid hit an ApolloSpacecraft, respectivamente, novamente sem despacho dinâmico.

Com uma referência a um Asteroid, despacho dinâmico é usado e este código:

Asteroid & theAsteroidReference = theExplodingAsteroid ;   
theAsteroidReference . CollideWith ( a Nave Espacial ); theAsteroidReference . CollideWith ( theApolloSpacecraft ); 

prints ExplodingAsteroid hit a SpaceShipe ExplodingAsteroid hit an ApolloSpacecraft, novamente como esperado. No entanto, o código a seguir não funciona como desejado:

SpaceShip & theSpaceShipReference = theApolloSpacecraft ;   
oAsteróide . CollideWith ( theSpaceShipReference );
theAsteroidReference . CollideWith ( theSpaceShipReference );

O comportamento desejado é vincular essas chamadas à função que recebe theApolloSpacecraftcomo argumento, pois esse é o tipo instanciado da variável, significando que a saída esperada seria Asteroid hit an ApolloSpacecrafte ExplodingAsteroid hit an ApolloSpacecraft. No entanto, a saída é na verdade Asteroid hit a SpaceShipe ExplodingAsteroid hit a SpaceShip. O problema é que, enquanto as funções virtuais são despachadas dinamicamente em C++, a sobrecarga de funções é feita estaticamente.

O problema descrito acima pode ser resolvido simulando despacho duplo, por exemplo, usando um padrão de visitante . Suponha que o código existente seja estendido para que ambos SpaceShipe ApolloSpacecraftrecebam a função

virtual void CollideWith ( Asteroid & inAsteroid ) {    
  em Asteróide . CollideWith ( * this );
}

Então, enquanto o exemplo anterior ainda não funciona corretamente, reenquadrar as chamadas para que a nave seja o agente nos dá o comportamento desejado:

SpaceShip & theSpaceShipReference = theApolloSpacecraft ;   
Asteroid & theAsteroidReference = theExplodingAsteroid ;   
theSpaceShipReference . CollideWith ( theAsteroid );
theSpaceShipReference . CollideWith ( theAsteroidReference );

Ele imprime Asteroid hit an ApolloSpacecrafte ExplodingAsteroid hit an ApolloSpacecraft, como esperado. A chave é que theSpaceShipReference.CollideWith(theAsteroidReference);faz o seguinte em tempo de execução:

  1. theSpaceShipReferenceé uma referência, então C++ procura o método correto na vtable. Nesse caso, ele chamará ApolloSpacecraft::CollideWith(Asteroid&).
  2. Dentro ApolloSpacecraft::CollideWith(Asteroid&)de , inAsteroidé uma referência, então inAsteroid.CollideWith(*this)resultará em outra pesquisa de vtable . Neste caso, inAsteroidé uma referência a um ExplodingAsteroidassim ExplodingAsteroid::CollideWith(ApolloSpacecraft&)será chamado.

Despacho duplo em C#

Em C# , ao chamar um método de instância aceitando um argumento, vários despachos podem ser obtidos sem empregar o padrão de visitante. Isso é feito usando o polimorfismo tradicional enquanto também lança o argumento para dynamic . [3] O fichário de tempo de execução escolherá a sobrecarga de método apropriada em tempo de execução. Essa decisão levará em consideração o tipo de tempo de execução da instância do objeto (polimorfismo), bem como o tipo de tempo de execução do argumento.

Despacho duplo em Eiffel

A linguagem de programação Eiffel pode trazer o conceito de agentes para o problema de despacho duplo. O exemplo abaixo aplica a construção da linguagem do agente ao problema de despacho duplo.

Considere um domínio de problema com várias formas de FORMA e de desenho de SUPERFÍCIE sobre o qual desenhar uma FORMA. Tanto SHAPE quanto SURFACE conhecem uma função chamada 'draw' em si mesmas, mas não uma na outra. Queremos que os objetos dos dois tipos interajam de forma covariante entre si em um despacho duplo usando um padrão de visitante.

O desafio é fazer com que uma SUPERFÍCIE polimórfica desenhe uma FORMA polimórfica sobre si mesma.

Saída

O exemplo de saída abaixo mostra os resultados de dois objetos de visitante SURFACE sendo polimorficamente passados ​​por uma lista de objetos SHAPE polimórficos. O padrão de código do visitante está ciente apenas de SHAPE e SURFACE genericamente e não do tipo específico de ambos. Em vez disso, o código depende do polimorfismo em tempo de execução e da mecânica dos agentes para obter um relacionamento covariante altamente flexível entre essas duas classes diferidas e seus descendentes.

desenhe um POLÍGONO vermelho no ETCHASKETCH
desenhe um POLÍGONO vermelho em GRAFFITI_WALL
desenhe um RETÂNGULO cinza em ETCHASKETCH
desenhe um RETÂNGULO cinza em GRAFFITI_WALL
desenhe um QUADRILATERAL verde no ETCHASKETCH
desenhe um QUADRILATERAL verde em GRAFFITI_WALL
desenhe um PARALELOGRAMA azul no ETCHASKETCH
desenhe um PARALELOGRAM azul em GRAFFITI_WALL
desenhe um POLÍGONO amarelo em ETCHASKETCH
desenhe um POLÍGONO amarelo em GRAFFITI_WALL
desenhe um RETÂNGULO roxo no ETCHASKETCH
desenhe um RETÂNGULO roxo em GRAFFITI_WALL

Configuração

Antes de olhar para SHAPE ou SURFACE, precisamos examinar o uso desacoplado de alto nível de nosso despacho duplo.

Padrão de visitante

O padrão visitante funciona por meio de um objeto visitante visitando os elementos de uma estrutura de dados (por exemplo, lista, árvore e assim por diante) polimorficamente, aplicando alguma ação (chamada ou agente) contra os objetos elementos polimórficos na estrutura alvo visitada.

Em nosso exemplo abaixo, fazemos uma lista de objetos SHAPE polimórficos, visitando cada um deles com uma SUPERFÍCIE polimórfica, solicitando que a SHAPE seja desenhada na SUPERFÍCIE.

	faço
			-- Imprima formas em superfícies.
		local
			l_shapes :  ARRAYED_LIST  [ SHAPE ]
			l_surfaces :  ARRAYED_LIST  [ SURFACE ]
		Faz
			crie  l_shapes . fazer  ( 6 )
			l_formas . extend  ( create  { POLYGON }. make_with_color  ( "red" ))
			l_formas . extend  ( criar  { RETÂNGULO }. make_with_color  ( "cinza" ))
			l_formas . extend  ( create  { QUADRILATERAL }. make_with_color  ( "verde" ))
			l_formas . extend  ( create  { PARALLELOGRAM }. make_with_color  ( "blue" ))
			l_formas . extend  ( create  { POLYGON }. make_with_color  ( "yellow" ))
			l_formas . extend  ( create  { RETÂNGULO }. make_with_color  ( "roxo" ))

			crie  l_surfaces . fazer  ( 2 )
			l_superfícies . estender  ( criar  { ETCHASKETCH }. make )
			l_superfícies . estender  ( criar  { GRAFFITI_WALL }. make )

			em  l_shapes  como  loop ic_shapes 
				em  l_surfaces  como  loop ic_surfaces 
					ic_surfaces . artigo . Drawing_agent  ( ic_shapes . item . Drawing_data_agent )
				fim
			fim
		fim

Começamos criando uma coleção de objetos SHAPE e SURFACE. Em seguida, iteramos sobre uma das listas (SHAPE), permitindo que elementos da outra (SURFACE) visitem cada uma delas por vez. No código de exemplo acima, objetos SURFACE estão visitando objetos SHAPE.

O código faz uma chamada polimórfica em {SURFACE}.draw indiretamente por meio do `drawing_agent', que é a primeira chamada (dispatch) do padrão de despacho duplo. Ele passa um agente indireto e polimórfico (`drawing_data_agent'), permitindo que nosso código de visitante saiba apenas duas coisas:

  • Qual é o agente de desenho da superfície (por exemplo, al_surface.drawing_agent na linha #21)?
  • Qual é o agente de dados de desenho da forma (por exemplo, al_shape.drawing_data_agent na linha #21)?

Como tanto o SURFACE quanto o SHAPE definem seus próprios agentes, nosso código de visitante não precisa saber qual é a chamada apropriada a fazer, polimorficamente ou não. Esse nível de indireção e desacoplamento simplesmente não é alcançável em outras linguagens comuns, como C, C++ e Java, exceto por meio de alguma forma de reflexão ou sobrecarga de recursos com correspondência de assinatura.

SUPERFÍCIE

Dentro da chamada polimórfica para {SURFACE}.draw está a chamada para um agente, que se torna a segunda chamada ou despacho polimórfico no padrão de despacho duplo.

	 aula adiada
		SUPERFÍCIE
	
	recurso  { NENHUM }  -- Inicialização
	
		faço
				-- Inicializa a corrente.
			Faz
				Drawing_agent  :=  sorteio de agente 
			fim
	
	recurso  -- Acesso

		Drawing_agent :  PROCEDURE  [ ANY ,  TUPLE  [ STRING ,  STRING ]]
				-- Agente de desenho da Corrente.
	
	recurso  { NENHUM }  -- Implementação
	
		draw  ( a_data_agent :  FUNCTION  [ ANY ,  TUPLE ,  TUPLE  [ nome ,  cor :  STRING ]] )
				-- Desenha 'a_shape' no Current.
			local
				l_result :  TUPLE  [ nome ,  cor :  STRING ]
			Faz
				l_result  :=  a_data_agent  ( Void )
				print  ( "desenhar um "  +  l_result . color  +  " "  +  l_result . name  +  " on "  +  type  +  "%N" )
			fim
	
		tipo :  STRING
				-- Digite o nome de Atual.
			 fim adiado
	
	fim

O argumento do agente na linha #19 e a chamada na linha #24 são polimórficos e desacoplados. O agente é desacoplado porque o recurso {SURFACE}.draw não tem ideia em qual classe `a_data_agent' é baseado. Não há como saber de qual classe o agente de operação foi derivado, portanto, ele não precisa vir de SHAPE ou de um de seus descendentes. Esta é uma vantagem distinta dos agentes Eiffel sobre a herança única, ligação dinâmica e polimórfica de outras linguagens.

O agente é dinamicamente polimórfico em tempo de execução porque o objeto é criado no momento em que é necessário, dinamicamente, onde a versão da rotina objetivada é determinada naquele momento. O único conhecimento fortemente vinculado é do tipo Result da assinatura do agente, ou seja, uma TUPLE nomeada com dois elementos. No entanto, este requisito específico é baseado em uma demanda do recurso delimitador (por exemplo, a linha #25 usa os elementos nomeados da TUPLE para cumprir o recurso 'desenhar' de SURFACE), o que é necessário e não foi evitado (e talvez não possa ser) .

Finalmente, observe como apenas o recurso `drawing_agent' é exportado para QUALQUER cliente! Isso significa que o código padrão do visitante (que é o ÚNICO cliente desta classe) precisa apenas saber sobre o agente para realizar seu trabalho (por exemplo, usando o agente como o recurso aplicado aos objetos visitados).

FORMA

A classe SHAPE tem a base (por exemplo, dados de desenho) para o que é desenhado, talvez em uma SUPERFÍCIE, mas não precisa ser. Novamente, os agentes fornecem a indireção e a agnóstica de classe necessária para tornar o relacionamento covariante com SHAPE o mais desacoplado possível.

Além disso, observe o fato de que o SHAPE fornece apenas `drawing_data_agent' como um recurso totalmente exportado para qualquer cliente. Portanto, a única maneira de interagir com o SHAPE, além da criação, é através das facilidades do `drawing_data_agent', que é usado por QUALQUER cliente para coletar dados de desenho de forma indireta e polimórfica para o SHAPE!

	 aula adiada
		FORMA
	
	recurso  { NENHUM }  -- Inicialização
	
		make_with_color  ( a_color :  like  color )
				-- Faça com `a_color' como `color'.
			Faz
				cor  :=  a_color
				Drawing_data_agent  :=  agente  Drawing_data
			garantir
				color_set :  cor . mesma_string  ( a_color )
			fim

	recurso  -- Acesso
	
		Drawing_data_agent :  FUNCTION  [ ANY ,  TUPLE ,  like  Drawing_data ]
				-- Agente de dados para desenho.
	
	recurso  { NENHUM }  -- Implementação
	
		Drawing_data :  TUPLE  [ nome :  como  nome ;  cor :  como  a cor ]
				-- Dados necessários para desenho de corrente.
			Faz
				Resultado  :=  [ nome ,  cor ]
			fim
	
		nome :  STRING
				-- Nome do objeto atual.
			 fim adiado
	
		cor :  STRING
				-- Cor da corrente.

	fim

Exemplo clássico de nave espacial

Uma variação do exemplo clássico de nave espacial tem um ou mais objetos de nave espacial vagando por um universo cheio de outros itens como asteróides e estações espaciais. O que queremos é um método de despacho duplo para lidar com encontros (por exemplo, possíveis colisões) entre dois objetos covariantes em nosso universo de faz de conta. Em nosso exemplo abaixo, a excursão de saída do nosso USS Enterprise e USS Excelsior será:

Starship Enterprise muda de posição de A-001 para A-002.
A Starship Enterprise toma uma ação evasiva, evitando o Asteroid `Rogue 1'!
A Starship Enterprise muda de posição de A-002 para A-003.
A Starship Enterprise toma uma ação evasiva, evitando o Asteroid `Rogue 2'!
A Starship Enterprise envia uma equipe científica para a Starship Excelsior enquanto eles passam!
Starship Enterprise muda de posição de A-003 para A-004.
A Starship Excelsior muda de posição de A-003 para A-005.
A Starship Enterprise toma uma ação evasiva, evitando o Asteroid `Rogue 3'!
O Starship Excelsior está perto da Estação Espacial Deep Space 9 e é acoplável.
A Starship Enterprise muda de posição de A-004 para A-005.
A Starship Enterprise envia uma equipe científica para a Starship Excelsior enquanto eles passam!
A Starship Enterprise está perto da Estação Espacial Deep Space 9 e é acoplável.

Visitante

O visitante do exemplo clássico da nave espacial também possui um mecanismo de despacho duplo.

faço
		-- Permitir que objetos SPACESHIP visitem e se movam em um universo.
	local
		l_universe :  ARRAYED_LIST  [ SPACE_OBJECT ]
		l_empresa ,
		l_excelsior :  NAVE ESPACIAL
	Faz
		crie  l_enterprise . make_with_name  ( "Empresa" ,  "A-001" )
		crie  l_excelsior . make_with_name  ( "Excelsior" ,  "A-003" )
		crie  l_universe . fazer  ( 0 )
		l_universo . força  ( l_enterprise )
		l_universo . force  ( create  { ASTEROID }. make_with_name  ( "Rogue 1" ,  "A-002" ))
		l_universo . force  ( create  { ASTEROID }. make_with_name  ( "Rogue 2" ,  "A-003" ))
		l_universo . força  ( l_excelsior )
		l_universo . force  ( create  { ASTEROID }. make_with_name  ( "Rogue 3" ,  "A-004" ))
		l_universo . force  ( create  { SPACESTATION }. make_with_name  ( "Deep Space 9" ,  "A-005" ))
		visite  ( l_enterprise ,  l_universe )
		l_empresa . set_position  ( "A-002" )
		visite  ( l_enterprise ,  l_universe )
		l_empresa . set_position  ( "A-003" )
		visite  ( l_enterprise ,  l_universe )
		l_empresa . set_position  ( "A-004" )
		l_excelsior . set_position  ( "A-005" )
		visite  ( l_enterprise ,  l_universe )
		visite  ( l_excelsior ,  l_universe )
		l_empresa . set_position  ( "A-005" )
		visite  ( l_enterprise ,  l_universe )
	fim
recurso  { NENHUM }  -- Implementação
visit  ( a_object :  SPACE_OBJECT ;  a_universe :  ARRAYED_LIST  [ SPACE_OBJECT ] )
		-- `a_object' visita `a_universe'.
	Faz
		através de  a_universe  como  loop ic_universe 
			verifique  anexado  { SPACE_OBJECT }  ic_universe . item  como  al_universe_object  então
				a_object . encontro_agente . call  ( [ al_universe_object . sensor_data_agent ] )
			fim
		fim
	fim

O despacho duplo pode ser visto na linha #35, onde dois agentes indiretos estão trabalhando juntos para fornecer duas chamadas covariantes trabalhando em perfeito concerto polimórfico uma com a outra. O `a_object' do recurso `visit' tem um `encounter_agent' que é chamado com os dados do sensor do `sensor_data_agent' vindos do `al_universe_object'. A outra parte interessante deste exemplo em particular é a classe SPACE_OBJECT e seu recurso 'encontro':

Ação do visitante

Os únicos recursos exportados de um SPACE_OBJECT são os agentes para dados de encontro e sensor, bem como a capacidade de definir uma nova posição. À medida que um objeto (a nave espacial) visita cada objeto no universo, os dados do sensor são coletados e passados ​​para o objeto visitante em seu agente de encontro. Lá, os dados do sensor do sensor_data_agent (ou seja, os itens de elemento de dados da TUPLE sensor_data conforme retornado pela consulta sensor_data_agent) são avaliados em relação ao objeto atual e um curso de ação é tomado com base nessa avaliação (consulte `encontro' em SPACE_OBJECT abaixo). Todos os outros dados são exportados para {NONE}. Isso é semelhante aos escopos C, C++ e Java de Private. Como recursos não exportados, os dados e rotinas são usados ​​apenas internamente por cada SPACE_OBJECT. Finalmente, observe que as chamadas de encontro para 'print' não inclua informações específicas sobre possíveis classes descendentes de SPACE_OBJECT! A única coisa encontrada neste nível na herança são aspectos relacionais gerais baseados completamente no que pode ser conhecido a partir dos atributos e rotinas de um SPACE_OBJECT geral. O fato de que a saída da 'impressão' faça sentido para nós, como seres humanos, com base no que sabemos ou imaginamos sobre naves estelares, estações espaciais e asteróides é apenas um planejamento lógico ou coincidência. O SPACE_OBJECT não é programado com nenhum conhecimento específico de seus descendentes. como seres humanos, com base no que sabemos ou imaginamos sobre naves estelares, estações espaciais e asteróides é apenas um planejamento lógico ou coincidência. O SPACE_OBJECT não é programado com nenhum conhecimento específico de seus descendentes. como seres humanos, com base no que sabemos ou imaginamos sobre naves estelares, estações espaciais e asteróides é apenas um planejamento lógico ou coincidência. O SPACE_OBJECT não é programado com nenhum conhecimento específico de seus descendentes.

 aula adiada
SPACE_OBJECT
recurso  { NENHUM }  -- Inicialização
make_with_name  ( a_name :  like  name ;  a_position :  like  position )
    -- Inicializa Current com `a_name' e `a_position'.
  Faz
    nome  :=  a_name
    posição  :=  a_posição
    sensor_data_agent  :=  agente  sensor_data
    encontro_agente  :=  encontro do agente 
  garantir
    name_set :  nome . mesma_string  ( a_name )
    position_set :  posição . mesma_string  ( a_position )
  fim
recurso  -- Acesso
find_agent :  PROCEDURE  [ QUALQUER ,  TUPLE ]
    -- Agente para gerenciamento de encontros com Current.
sensor_data_agent :  FUNCTION  [ ANY ,  TUPLE ,  anexado  como  sensor_data_anchor ]
    -- Agente para retorno de dados do sensor de Current.
recurso  -- Configurações
set_position  ( a_position :  like  position )
    -- Definir 'posição' com 'a_posição'.
  Faz
    print  ( type  +  " "  +  name  +  " muda a posição de "  +  position  +  " para "  +  a_position  +  ".%N" )
    posição  :=  a_posição
  garantir
    position_set :  posição . mesma_string  ( a_position )
  fim
recurso  { NENHUM }  -- Implementação
encontro  ( a_sensor_agent :  FUNCTION  [ ANY ,  TUPLE ,  anexado  como  sensor_data_anchor ] )
    -- Detecta e relata o status de colisão do Current com `a_radar_agent'.
  Faz
    a_sensor_agent . chamar  ( [ Vazio ] )
    verifique  anexado  { like  sensor_data_anchor }  a_sensor_agent . last_result  como  al_sensor_data  então
      se  não  nome . same_string  ( al_sensor_data . name )  então
        if  ( position . same_string  ( al_sensor_data . position ))  then
          if  (( al_sensor_data . is_dockable  and  is_dockable )  e
              ( is_manned  e  al_sensor_data . is_manned )  e
              ( is_manueverable  e  al_sensor_data . is_not_manueverable ))  então
            print  ( type  +  " "  +  name  +  " está próximo de "  +  al_sensor_data . type  +  " "  +
                al_sensor_data . name  +  " e é encaixável.%N" )
          elseif  (( is_dockable  e  al_sensor_data . is_dockable )  e
                ( is_manned  e  al_sensor_data . is_manned )  e
                ( is_manueverable  e  al_sensor_data . is_manueverable ))  então
            print  ( type  +  " "  +  name  +  " envia uma equipe científica para "  +  al_sensor_data . type  +  " "  +
                al_sensor_data . nome  +  " conforme eles passam!%N" )
          elseif  ( is_manned  e  al_sensor_data . is_not_manned )  then
            print  ( tipo  +  " "  +  nome  +  " faz uma ação evasiva, evitando "  +
                al_sensor_data . digite  +  " `"  +  al_sensor_data . nome  +  "'!%N" )
          fim
        fim
      fim
    fim
  fim
nome :  STRING
    -- Nome da Atual.
tipo :  STRING
    -- Tipo de Corrente.
  adiado
  fim
posição :  STRING
    -- Posição da Corrente.
is_dockable :  BOOLEAN
    -- O Current é acoplável a outro objeto tripulado?
  adiado
  fim
is_manned :  BOOLEAN
    -- O Current é um objeto tripulado?
  adiado
  fim
is_manueverable :  BOOLEAN
    -- A corrente pode ser movida?
  adiado
  fim
sensor_data :  anexado  como  sensor_data_anchor
    -- Dados do sensor de corrente.
  Faz
      Resultado  :=  [ name ,  type ,  position ,  is_dockable ,  not  is_dockable ,  is_manned ,  not  is_manned ,  is_manueverable ,  not  is_manueverable ]
    fim

  sensor_data_anchor :  destacável  TUPLE  [ nome ,  tipo ,  posição :  STRING ;  is_dockable ,  is_not_dockable ,  is_manned ,  is_not_manned ,  is_manueverable ,  is_not_manueverable :  BOOLEAN ]
      -- Âncora do tipo de dados do sensor de Corrente.

fim

Existem três classes descendentes de SPACE_OBJECT:

SPACE_OBJECT 
ASTEROID ESTAÇÃO 
DE NAVE 
ESPACIAL

Em nosso exemplo, a classe ASTEROID é usada para os itens 'Rogue', SPACESHIP para as duas naves estelares e SPACESTATION para Deep Space Nine. Em cada classe, a única especialização é a configuração do recurso 'tipo' e de certas propriedades do objeto. O 'nome' é fornecido na rotina de criação assim como a 'posição'. Por exemplo: Abaixo está o exemplo da NAVE ESPACIAL.

classe
NAVE ESPACIAL
herdar
SPACE_OBJECT
crio
make_with_name
recurso  { NENHUM }  -- Implementação
tipo :  STRING  =  "Nave Estelar"
  -- <Precursor>
is_dockable :  BOOLEAN  =  True
  -- <Precursor>
is_manned :  BOOLEAN  =  True
  -- <Precursor>
is_manueverable :  BOOLEAN  =  True
  -- <Precursor>
fim

Assim, qualquer NAVE ESPACIAL em nosso universo pode ser acoplada, tripulada e manobrável. Outros objetos, como asteróides, não são nada disso. Uma SPACESTATION, por outro lado, pode ser acoplada e tripulada, mas não é manobrável. Assim, quando um objeto se encontra com outro, ele primeiro verifica se as posições os colocam próximos uns dos outros e, se estiverem, os objetos interagem com base em suas propriedades básicas. Observe que objetos com o mesmo tipo e nome são considerados para o mesmo objeto, portanto, uma interação não é logicamente permitida.

Conclusão do exemplo Eiffel

Com relação ao despacho duplo, Eiffel permite que o designer e o programador removam ainda mais um nível de conhecimento direto de objeto a objeto ao desacoplar rotinas de classe de suas classes por meio de torná-los agentes e, em seguida, passar esses agentes em vez de fazer recurso de objeto direto chamadas. Os agentes também possuem assinaturas específicas e resultados possíveis (no caso de consultas), tornando-os ideais para verificação de tipo estáticoveículos sem abrir mão de detalhes específicos do objeto. Os agentes são totalmente polimórficos para que o código resultante tenha apenas o conhecimento específico necessário para realizar seu trabalho local. Caso contrário, não há carga de manutenção adicionada por ter conhecimento de recursos de classe interna específicos espalhados por muitos objetos covariantes. O uso e a mecânica dos agentes garantem isso. Uma possível desvantagem do uso de agentes é que um agente é computacionalmente mais caro do que sua contraparte de chamada direta. Com isso em mente, nunca se deve presumir o uso de agentes no despacho duplo e sua aplicação em padrões de visitantes. Se pudermos ver claramente um limite de projeto quanto ao domínio dos tipos de classe que estarão envolvidos nas interações covariantes, então uma chamada direta é a solução mais eficiente em termos de despesas computacionais. No entanto,

Veja também

Referências

  1. ^ Uma técnica simples para segurar o polimorfismo múltiplo. In Proceedings of OOPSLA '86, Object-Oriented Programming Systems, Languages ​​and Applications, páginas 347–349, novembro de 1986. Impresso como SIGPLAN Notices, 21(11). ISBN  0-89791-204-7
  2. ^ Mais Eficaz C++ por Scott Meyers (Addison-Wesley, 1996)
  3. ^ "Usando o tipo dinâmico (guia de programação C#)" . Rede de desenvolvedores da Microsoft . Microsoft. 30 de setembro de 2009 . Recuperado em 25 de maio de 2016 . ... A resolução de sobrecarga ocorre em tempo de execução em vez de em tempo de compilação se um ou mais argumentos em uma chamada de método tiverem o tipo dynamic ...