Herança de UserObjects e ordem de execução de eventos/funções no PowerBuilder

Neste post, faço um pequeno estudo sobre a ordem de execução de funções e eventos nos UserObjects do PowerBuilder, mostrando quais eventos/funções são disparados primeiro, quais não são e por que.

Cenário

  1. Primeiro, criamos o objeto uo_principal;
  2. No evento constructor deste objeto, inserimos um MessageBox com a mensagem “Constructor – uo_principal“;
  3. Criamos um evento chamado ue_event que não retorna nada e não possui argumentos. Nele existe apenas um Messagebox com a mensagem “Evento – uo_principal“;
  4. Ainda no objeto uo_principal, criamos uma função chamada uf_function que, assim como o evento, não possui retorno ou argumentos, apenas um MessageBox com a mensagem “Função – uo_principal“;

 

Agora fechamos o painter deste objeto e criamos outro, herdando do uo_principal, realizando os seguintes passos:

  1. No constructor do objeto, inserimos um MessageBox com a mensagem “Constructor – uo_secundario“;
  2. No evento ancestral ue_event, inserimos um MessageBox com a mensagem: “Evento – uo_secundario“;
  3. Na função ancestral uf_function, inserimos um MessageBox com a mensagem: “Função – uo_secundario“;
  4. Salvamos o objeto com o nome uo_secundario;

 

Comportamentos

Se eu criar uma instância do uo_secundario, quais mensagens serão exibidas e em qual ordem?

No comportamento natural do PowerBuilder, ou seja, sem que façamos alterações manuais, os eventos dos ancestrais sempre são disparados antes dos eventos do próprio objeto.

Sendo assim, vamos ver as mensagens na seguinte ordem:

  1. “Constructor – uo_principal”
  2. “Constructor – uo_secundario”

 

 

Se dispararmos o evento ue_event, também veremos as mensagens do ancestral primeiro e depois a do próprio objeto?

Sim. Parece obvio falar, mas o Constructor é um evento como outro qualquer (em termos de estrutura)  e assim como todos os outros, obedece a mesma ordem de execução: Primeiro os ancestrais, depois o próprio objeto. Isso vale para objetos que possuem vários níveis de herança, ou seja, se criarmos o uo_terciario, herdadando ele do uo_secundario, ele vai começar a executar os eventos a partir do uo_principal e depois ir seguindo a ordem de herança até chegar no uo_terciario. Isso tudo, obviamente, se ninguém interferir na ordem de execução.

 

Ok. Então a mesma coisa vai acontecer se executarmos a função uf_function() na instância que criamos do uo_secundario, certo?

Não! Este encadeamento automático nas execuções acontece com eventos e não com funções.

 

Tecnicamente, por que somente os eventos são executados nesta ordem? Como funciona?

Esta pergunta é interessante e a resposta é até simples: Quando criamos um objeto herdado, o PowerBuilder ‘esconde’ uma chamada para o evento do ancestral como a primeira instrução do evento, mas, apesar de ser possível realizar o mesmo procedimento para as funções, o PowerBuilder não faz isso.

Você pode desabilitar isso desmarcando a opção “Extend Ancestor Script“. Neste caso, apenas o que foi definido no evento vai ser executado.

 

Abaixo está o exemplo do fonte do objeto uo_secundario:

event ue_event;call super::ue_event;messagebox('','Evento - uo_secundario')
end event

event constructor;call super::constructor;messagebox('','Constructor - uo_secundario')
end event

Note que, logo após a definição do evento, existe o a chamada para o evento ancestral (call super::ue_event). Este comando é executado antes do resto do código.

 

Agora vamos comparar o código do evento com o da função:

forward prototypes
public subroutine uf_function ()
end prototypes

public subroutine uf_function ();messagebox('','Function - uo_secundario')
end subroutine

Nele temos a declaração da função (forward prototypes) e a definição do fonte da função. Perceba que o PowerBuilder não incluiu uma chamada para a função ancestral, ou seja, apenas o código definido na função uf_function do objeto atual vão ser executados.

Seja qual for a razão, o PowerBuilder não inclui esta chamada, mas nem tudo está perdido. Temos 2 formas de resolver esta situação: A recomendada e a gambiarra.

 

Neste momento você me pergunta: Mas Guaxinim, quando eu herdo um objeto, as funções do ancestral são executadas perfeitamente. Você está me enganando?

Claro que não. Quando a função está em um objeto herdado, mas está sem código no novo objeto, ele executa o que estiver no ancestral. Exemplo: Se eu apagar todo o código da função uf_function do uo_secundario, o que será executado é o que está programado na função uf_function do ancestral (uo_principal).

 

Soluções para o encadeamento na execução de funções

Solução Recomendada

A solução recomendada que encontramos na internet e a que, certamente, é recomendada pela Sybase/SAP/Appeon é a mais simples: basta inserir (manualmente) a chamada para função ancestral.

Veja o fonte alterado da função uf_function do objeto uo_secundario:

//Chama função ancestral...
super:: uf_function()

//Executa função local...
messagebox('','Function - uo_secundario')

Nele, adicionamos uma linha de código (duas, se contar com a de comentário) para garantir que a  função do objeto ancestral seja executada primeiro.

Esta solução é exatamente a mesma que o PowerBuilder implementa nativamente nos eventos, mas de forma visível.

 

Gambiarra

Eu sei, não devia estar passando este tipo de dica. Sei que você nunca implementaria uma gambiarra, mas vou falar mesmo assim. Vai que você está fazendo manutenção em um sistema que já possui gambiarras e você precisa removê-las…

Afinal, qual é a “solução gambiarra”? Simples. É essa aqui:

forward prototypes
public subroutine uf_function ()
end prototypes

public subroutine uf_function ();super:: uf_function();
//Executa função local...
messagebox('','Function - uo_secundario')
end subroutine

O que tem de gambiarra aí? A chamada para a função ancestral escondida no fonte do objeto. Logo após a declaração da função, existe uma chamada para a função ancestral (super:: uf_function()) entre dois ;. Isso faz com que este comando não seja exibido no fonte, mas seja executado mesmo assim.

Por que isso é gambiarra? Isso não deve ser feito. Você não tem controle do que o PowerBuilder vai fazer com este código escondido. Pode ser que ele seja removido ou que faça o objeto apresentar comportamentos anômalos. Outro ponto importante é que você vai acabar esquecendo que colocou este fonte aí ou não vai saber que alguém escondeu uma chamada no fonte deste objeto e vai gastar horas tentando descobrir o que está de errado.

Só para deixar claro, mostrei esta opção para te ajudar a remover gambiarras existentes.

 

Ainda está um pouco confuso?

Para ajudar, fiz dois objetos (uo_main e uo_inherited) e os coloquei no meu github juntamente com uma aplicação de exemplo (workspace e executavel).

 

 

Obrigado ao Armando pela dica deste post! 🙂

 

Espero ter ajudado!

The following two tabs change content below.
Arquiteto de Software e Desenvolvedor Backend (quase Fullstack), geralmente trabalho com C#, PowerShell, Python, Golang, bash e Unity (esse é mais por hobby). Estou sempre buscando algo novo para aprender, adicionando novas ferramentas ao meu cinto de utilidades.
Posted in Dev, PowerBuilder and tagged , , .