Você está na página 1de 13

Introdução de POO no VBA

O VBA suporta o uso de classes através de componentes chamados módulos de classe.


Classes são usadas para criar objetos.

Introdução
Uma analogia bastante utilizada é dizermos que se um bolo é um objeto, sua receita é uma
classe. Seguindo o raciocínio, é possível fazer vários bolos a partir de uma receita, ou vários
objetos a partir de uma classe. Uma classe não aloca memória em tempo de execução, e um
objeto sim, já que a classe possui apenas a definição do objeto que cria.

Uma classe descreve as propriedades e métodos de um objeto. Propriedades podem ser


entendidas como características de um objeto, e métodos, ações que o mesmo promove. Por
exemplo, considere o objeto Carro. Entre suas propriedades, podemos citar cor,
quilometragem, chassi, marca, modelo, etc. Os métodos poderiam ser ações como dar a
partida, acionar o para-brisa, frear, buzinar. Se fizermos a analogia que uma propriedade é um
adjetivo, certamente um método é um verbo.

Declarar, Instanciar e Destruir um Objeto a Partir de uma


Classe.
Os objetos possuem um ciclo de vida. Primeiro, devem ser declarados. Então, são criados
(tecnicamente é melhor falar instanciados), depois são utilizados (ou consumidos) e, for fim,
são destruídos.
No VBA, um objeto é instanciado no momento em que a palavra chave New é utilizada.

Sub CriarObjeto()
'Declarar:
Dim oCarro As clsCarro

'Instanciar:
Set oCarro = New clsCarro

'Consumir:
'...

'Destruir:
Set oCarro = Nothing

End Sub

oCarro é o objeto e clsCarro é a classe. Repare que quando trabalhamos com objetos, as
atribuições devem ser feitas com o uso da palavra chave Set, ao contrário de tipos de dados
simples como Long, Integer, Date, etc., em que o uso da igualdade dá valor à variável.
Para que o exemplo acima funcione, é necessário que criemos a classe clsCarro no nosso
projeto VBA:
Embora não seja obrigatório, é altamente recomendável destruir todos os objetos criados ao
término da execução do seu programa. O gerenciamento de memória e coletor de lixo do VBE
não são bons, e ao adotar essa prática você minimiza erros inesperados e até crashes no
Excel.

Consumir Objetos
O exemplo a seguir mostra como acessar membros de um objeto de uma classe
chamada clsEmpregado. Pelo código, podemos ver que a classe define três
propriedades: Nome, Endereço e Salário. Cole o num Módulo de Classe
chamado clsEmpregado:

'Esta Classe define três propriedades: Nome, Endereço e Salário.


'Seus valores são armazenados, respectivamente,
'nas variáveis aNome, aEndereço e aSalário.

Private aNome As String


Private aEndereço As String
Private aSalário As Double

'Abaixo seguem as declarações de propriedade de como são lidas e gravadas.


'Propriedade Nome:
Property Get Nome() As String
Nome = aNome
End Property
Property Let Nome(pNome As String)
aNome = pNome
End Property

'Propriedade Endereço:
Property Get Endereço() As String
Endereço = aEndereço
End Property
Property Let Endereço(pEndereço As String)
aEndereço = pEndereço
End Property

'Propriedade Salário:
Property Get Salário() As Double
Salário = aSalário
End Property
Property Let Salário(pSalário As Double)
aSalário = pSalário
End Property

'Propriedade somente para leitura SalárioAnual:


Property Get SalárioAnual() As Double
SalárioAnual = Salário * 12
End Property

Public Sub MostrarFolhaDePagamento()


Dim sMensagem As String

sMensagem = sMensagem & "Nome: " & Nome & vbCrLf


sMensagem = sMensagem & "Data atual: " & Date & vbCrLf
sMensagem = sMensagem & vbNewLine
sMensagem = sMensagem & "Salário Mensal: " & Salário & vbCrLf
sMensagem = sMensagem & "Salário Anual: " & SalárioAnual & vbCrLf
MsgBox sMensagem, vbInformation
End Sub

Para cada uma das propriedades, há um procedimento Get e outro Let. Get é chamado
quando se deseja ler o valor de uma propriedade e Let é chamado quando se
deseja atribuir um valor a uma propriedade.
Para vermos nosso programa em funcionamento, devemos criar num módulo (comum, e não
de classe) o seguinte código:

Sub UsoBásicoDeClasses()
Dim oEmpregado As clsEmpregado
Set oEmpregado = New clsEmpregado

oEmpregado.Nome = "Felipe"
oEmpregado.Endereço = "Rua Jardim, 20/603"
oEmpregado.Salário = 1000

Debug.Print "Nome: " & oEmpregado.Nome


Debug.Print "Endereço: " & oEmpregado.Endereço
Debug.Print "Salário: " & oEmpregado.Salário

Set oEmpregado = Nothing


End Sub

Observe que ao escrever oEmpregado aparece o intellisense com todas as propriedades do


objeto:
A vantagem imediata em usar classes é ter uma visualização completa dos membros de um
objeto, além do ganho obtido em tempo de desenvolvimento.

Ao executar essa rotina, teremos como resultado na janela de verificação imediata (Ctrl+G):

Nome: Felipe
Endereço: Rua A, 12/901
Salário: 1000

Uso do With

Ao trabalhar com suas classes personalizadas, você pode usar o bloco With com os objetos
que você criar:

Sub UsoBásicoDeClassesComWith()
Dim oEmpregado As clsEmpregado

Set oEmpregado = New clsEmpregado


With oEmpregado
.Nome = "Felipe"
.Endereço = "Rua Jardim, 20/603"
.Salário = 1000

Debug.Print "Nome: " & .Nome


Debug.Print "Endereço: " & .Endereço
Debug.Print "Salário: " & .Salário
End With

Set oEmpregado = Nothing


End Sub

Atributos vs Propriedades vs Parâmetros

Dentro da classe, podemos observar, por exemplo Endereço, aEndereço e pEndereço. O que
está havendo? Para definir uma simples propriedade, utilizei três membros diferentes com
nomes semelhantes. Eles são necessários. Vamos rever a definição de atribuição de
propriedade Endereço:

Property Let Endereço(pEndereço As String)

aEndereço = pEndereço

End Property

Endereço é o nome da propriedade do objeto. A declaração de uma propriedade se assemelha


bastante com a declaração de um subprocedimento e, como sabemos, um procedimento por si
só não conseguem armazenar um valor após o término de sua execução. É para isso que
utilizamos uma variável para armazenar o valor da propriedade, que é representada aqui
por aEndereço (A letra a representa atributo). Ela é uma variável de nível de módulo, e retém
os valores de um objeto enquanto o objeto estiver alocado na memória. Então, a
propriedade Endereço é uma espécie de canal de comunicação entre a variável
interna aEndereço e o ambiente externo.
pEndereço (A letra p representa parâmetro) é nada mais que o parâmetro de entrada do
procedimento objeto. Ele possui o valor externo que será processado pela procedimento e
atribuído à variável aEndereço. Por fim:

Property Get Endereço() As String

Endereço = aEndereço

End Property

Endereço, nesse contexto, funciona igual uma função, que retorna o valor do
atributo aEndereço ao procedimento que chamou a propriedade Endereço.
Simplificando Declaração de uma Propriedade

Se você não precisar inserir código no procedimento Get e Let de uma variável, você pode
declarar a variável como pública que o VBE irá considera-lo como uma variável. Em outras
palavras, o bloco de código:

Private aNome As String

Property Get Nome() As String


Nome = aNome
End Property
Property Let Nome(pNome As String)
aNome = pNome
End Property

É equivalente a simplesmente:

Public Nome As String

Note que ao utilizar a palavra-chave Public numa variável que está dentro de um módulo de
classe, ela não é pública para todo projeto, como ocorre em declarações em módulos
regulares. Public em módulos de classe significa que a variável declarada pode ser acessada
por procedimentos que não estejam dentro da classe.

Propriedades Somente Como Leitura


No exemplo anterior, todas as propriedades possuíam a declaração Let e Get.
É possível criar uma propriedade somente como leitura. Para tal, poderíamos definir, por
exemplo, uma propriedade chamada SalárioAnual, que seria dada pelo produto do salário
mensal e 12:
'Propriedade somente para leitura SalárioAnual:
Property Get SalárioAnual() As Double
SalárioAnual = Salário * 12
End Property

Note que poderíamos ter utilizado a variável de módulo (que aqui tem a função de
atributo) aSalário ao invés da propriedade Salário. No entanto, as boas práticas de
programação dizem que quando estamos numa classe, devemos utilizar o valor retornado pela
propriedade, e não a variável que armazena o valor da propriedade. Essa boa prática tem um
custo que é tornar a depuração do código mais trabalhosa, uma vez que todo acesso à
propriedade Salário desvia o código para seu procedimento respectivo. Por outro lado, ao
usar o valor da propriedade, você terá garantido que o valor atribuído já foi processado e
validado por sua classe.
O fato dessa propriedade não possuir a instrução Let é o que a caracteriza somente como
leitura, tornando impossível fazer uma atribuição direta a ela.

Restringindo Valores de Entrada de uma Propriedade


Uma grande vantagem em usar classes é possibilidade de tratar os dados de entrada e saída
da classe. Para o exemplo da classe clsEmpregado, vamos adicionar código para ser
impossível atribuir um valor negativo à propriedade Salário. Poderíamos adaptar sua
propriedade Let na forma mostrada abaixo:

Property Let Salário(d As Double)


'Restringir valores de entrada para Salário:
If pSalário > 0 Then
aSalário = pSalário
Else
'O valor do salário deve ser maior que zero!
aSalário = 1
End If
End Property

Criar Métodos na Classe


Até agora foi mostrado apenas como trabalhar com propriedades nas classes. Você pode
definir métodos também. Acrescente o bloco de código abaixo na classe clsEmpregado:

Public Sub MostrarFolhaDePagamento()


Dim sMensagem As String

sMensagem = sMensagem & "Nome: " & Nome & vbCrLf


sMensagem = sMensagem & "Data atual: " & Date & vbCrLf
sMensagem = sMensagem & vbNewLine
sMensagem = sMensagem & "Salário Mensal: " & Salário & vbCrLf
sMensagem = sMensagem & "Salário Anual: " & SalárioAnual & vbCrLf
MsgBox sMensagem, vbInformation
End Sub

Agora, coloque o código abaixo num módulo comum e execute:

Sub ExemploMétodo()
Dim oEmpregado As clsEmpregado

Set oEmpregado = New clsEmpregado


oEmpregado.Nome = "Felipe"
oEmpregado.Endereço = "Rua Jardim, 20/603"
oEmpregado.Salário = 1000
oEmpregado.MostrarFolhaDePagamento

Set oEmpregado = Nothing


End Sub

O resultado será:

Referenciando Propriedades e Métodos da Classe


Quando escrevemos código dentro de uma classe, ao invés de
escrever Nome, Salário e SalárioAnual, podemos
escrever Me.Nome, Me.Salário e Me.SalárioAnual. Em outras palavras, no contexto dentro
de uma classe, Me se refere a ela mesma. Uma vantagem de se usar o Me é o fato de aparecer
o intellisense com todos os membros (isto é, propriedades, métodos, constantes e
enumerações) da classe:

Note que ícones de propriedades são diferentes de ícones de métodos.

Eventos Padrões de uma Classe


Toda classe no VBA possui dois eventos padrões, de nome fixo. Um chama-
se Class_Initialize, e é executado quando um objeto é instanciado. O
outro, Class_Terminate, é executado quando o objeto é destruído. Você não é obrigado a
colocar código nesses dois eventos.
Para exemplificar, crie uma classe chamada clsCasa com o código abaixo:

Private aEndereço As String


Private aDólares As Double

'Eventos
Private Sub Class_Initialize()
Me.Endereço = "Rua das Flores, 105"
End Sub
Private Sub Class_Terminate()
MsgBox "Um objeto cuja propriedade Endereço é " & Me.Endereço & " foi
destruído.", vbInformation
End Sub

'Propriedades
Property Get Endereço() As String
Endereço = aEndereço
End Property
Property Let Endereço(pEndereço As String)
aEndereço = pEndereço
End Property

Property Get Dólares() As Double


Dólares = aDólares
End Property
Property Let Dólares(pDólares As Double)
aDólares = pDólares
End Property

Num módulo regular, coloque o código abaixo:

Sub ExemploEventos()
Const VALOR_DÓLAR_DO_DIA = 3.03

Dim oCasa As clsCasa

'O evento Class_Initialize é chamado quando se cria o objeto:


Set oCasa = New clsCasa

oCasa.Dólares = 150000 * VALOR_DÓLAR_DO_DIA

'O evento Class_Terminate é chamado quando se destrói o objeto:


Set oCasa = Nothing
End Sub
Se você retirar a instrução Set oCasa = Nothing do código acima verá que o ponto de
execução desviará de End Sub para o procedimento destrutor do objeto (ou seja, foi destruído
implicitamente). Pode-se argumentar então que destruir um objeto explicitamente é
desnecessário. Volto a insistir que a melhor forma de destruir um objeto é explicitamente, e que
em muitos casos você, como desenvolvedor, terá menos problemas em sistemas mais
complexos e melhor gerenciamento de memória. A título de exemplo, existem alguns cenários
de crashes no Excel em formulários quando os mesmos não são destruídos explicitamente.
Infelizmente, pelo fato do problema ser também de design do VBE, não consigo descrever um
passo a passo para reproduzir esse tipo de problema. Além disso, um bom programa é aquele
que encerra quando a instrução End Sub do método pai é executada, sem disparar toneladas
de coletores de lixo dos objetos pendurados na memória.
Normalmente uso o evento Class_Initialize para definir valores iniciais e padrões de um
objeto. No exemplo acima, ao criar um objeto clsCasa, define-se automaticamente que a
propriedade Endereço do mesmo é Rua das Flores, 105.
Nesse sentido, você pode usar esse evento para definir propriedades padrão ao criar um
objeto. Suponha que você crie vários objetos de uma classe clsVeículo para usar num
ambiente em que os veículos são, predominantemente, carros. Se existir uma propriedade
chamada QuantidadeRodas, você poderia atribuir 4 à essa propriedade dentro do
evento Class_Initialize, e atribuir explicitamente oVeículo.QuantidadeRodas = 2 fora da
classe apenas nos casos de quando o veículo é uma moto.
O Class_Terminate é utilizado para colocar códigos de limpeza no ato da destruição de um
objeto. No nosso exemplo, mostra-se apenas uma notificação de que o objeto foi destruído.

Os Problemas da Auto Instanciação

Alternativamente, você pode criar um objeto dessa forma:

Sub CriarObjetoFormaNãoRecomendada()

'Declarar e instanciar:
Dim oCarro As New clsCarro

'Consumir
'...

'Destruir
Set oCarro = Nothing

End Sub

Foi feita a declaração e criada uma instância do objeto numa única instrução. O nome dessa
técnica é auto instanciação de variável. No VBA, não é recomendável utilizá-la por dois
motivos:

 Aumenta o overhead do código porque cada chamada a um objeto criado dessa classe irá
disparar o evento de inicialização do mesmo. Ao criar um objeto dessa forma e fazer uma
simples atribuição como, por exemplo, oCarro.Cor = "Verde", o
evento Class_Initialize será disparado, e isso é altamente indesejável.
 Não há como testar se uma variável criada desse tipo é Nothing porque a própria instrução
de teste irá criar uma instância do objeto, retornando, então, sempre Falsepara o teste.
Nesse exemplo, o teste If oCarro Is Nothing Then... sempre irá passar.
Criar mais de um Objeto com uma Classe
No exemplo a seguir, foram criados três objetos do mesmo tipo, mas com propriedades
diferentes:

Sub VáriosObjetos()
Dim oEmp1 As clsEmpregado
Dim oEmp2 As clsEmpregado
Dim oEmp3 As clsEmpregado

Set oEmp1 = New clsEmpregado


Set oEmp2 = New clsEmpregado
Set oEmp3 = New clsEmpregado

oEmp1.Nome = "Felipe"
oEmp1.Endereço = "Rua Jardim, 20/603"
oEmp1.Salário = 1000
'---
oEmp2.Nome = "Renata"
oEmp2.Endereço = "Praça das Flores, 305"
oEmp2.Salário = 1500
'---
oEmp3.Nome = "Rodrigo"
oEmp3.Endereço = "Av. Castro, 50/101"
oEmp3.Salário = 2000

oEmp1.MostrarFolhaDePagamento
oEmp2.MostrarFolhaDePagamento
oEmp3.MostrarFolhaDePagamento

Set oEmp1 = Nothing


Set oEmp2 = Nothing
Set oEmp3 = Nothing
End Sub

O VBA não mistura os valores das propriedades (na verdade atributos) dos objetos criados.
Cada objeto aloca espaço na memória para guardar os próprios valores de cada propriedade.

Criar uma Coleção de Objetos


Você pode adicionar objetos em coleções. Suponha que você tenha a tabela abaixo:
Use o código a seguir para criar um objeto por linha da tabela, povoar as propriedades da
tabela de acordo as colunas respectivas e, em seguida, mostrar os dados dos objetos na janela
de verificação imediata:

Sub ColeçãoDeObjetos()
Dim tblEmpregados As ListObject
Dim oEmpregado As clsEmpregado
Dim cEmpregados As Collection
Dim iListRow As ListRow
Dim iItem As Long

'Definir tabela de Empregados


Set tblEmpregados =
ThisWorkbook.Worksheets("Coleções").ListObjects("Empregados")

'Inicializar Coleção?
Set cEmpregados = New Collection

'Criar e popular objetos


For Each iListRow In tblEmpregados.ListRows
Set oEmpregado = New clsEmpregado
oEmpregado.Nome = iListRow.Range(1)
oEmpregado.Endereço = iListRow.Range(2)
oEmpregado.Salário = iListRow.Range(3)

'Adiciona objeto à coleção:


cEmpregados.Add oEmpregado
Next iListRow

'Mostrar resultados
For Each oEmpregado In cEmpregados
Debug.Print oEmpregado.Nome, oEmpregado.Endereço, oEmpregado.Salário
Next oEmpregado

'Ou, com laço do tipo For...Next


For iItem = 1 To cEmpregados.Count
Set oEmpregado = cEmpregados(iItem)
Debug.Print oEmpregado.Nome, oEmpregado.Endereço, oEmpregado.Salário
Next iItem

End Sub
Você pode remover um item da coleção se desejar. A instrução abaixo remove o terceiro item
da coleção do nosso exemplo:

cEmpregados.Remove 3

Gerador de Propriedades
Usando um “código para gerar código”, você pode tornar menos moroso o processo de
escrever declarações de propriedades:

Sub GerarPropriedade()
Dim sAtributo As String
Dim sInstrução As String
Dim sParâmetro As String
Dim sPropriedade As String
Dim sSaída As String
Dim sTipoDeDados As String
Dim vInstruções() As String

sInstrução = InputBox(Prompt:="Digite a linha de declaração do atributo:",


_
Default:="Private aNome As String")
If sInstrução = "" Then Exit Sub

vInstruções = Split(sInstrução)
sAtributo = vInstruções(1)
sPropriedade = Mid(sAtributo, 2)
sParâmetro = "p" & sPropriedade
sTipoDeDados = vInstruções(3)

sSaída = ""
sSaída = sSaída & "Property Get " & sPropriedade & "() As " & sTipoDeDados
& vbNewLine
sSaída = sSaída & vbTab & sPropriedade & " = " & sAtributo & vbNewLine
sSaída = sSaída & "End Property" & vbNewLine & vbNewLine
sSaída = sSaída & "Property Let " & sPropriedade & "(" & sParâmetro & " As
" & sTipoDeDados & ")" & vbNewLine
sSaída = sSaída & vbTab & sAtributo & " = " & sParâmetro & vbNewLine
sSaída = sSaída & "End Property" & vbNewLine
Debug.Print sSaída
End Sub

Se você entrar Private aNome As String na janela, obterá as declarações Get e Letna
janela de verificação imediata:

Property Get Nome() As String


Nome = aNome
End Property
Property Let Nome(pNome As String)
aNome = pNome
End Property

Você pode criar versões mais sofisticadas desse código, como por exemplo: obter o esquema
de uma tabela de um banco de dados e gerar classes no VBE mapeando cada um dos campos
a uma propriedade. Use a criatividade. Não deixe de verificar também o MZ Tools, que possui
um ótimo assistente para criar propriedades em classes.

Fontes:

AmbienteOffice Publicado em 22 de abril de 2015 por Felipe Gualberto