Introdução a Módulos de Classe 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.

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 msNome, msEndereço e mdSalário.
 
Dim msNome As String
Dim msEndereço As String
Dim mdSalário As Double
    
'Abaixo seguem as declarações de propriedade de como são lidas e gravadas.
'Propriedade Nome:
Property Let Nome(s As String)
    msNome = s
End Property
Property Get Nome() As String
    Nome = msNome
End Property
 
'Propriedade Endereço:
Property Let Endereço(s As String)
    msEndereço = s
End Property
Property Get Endereço() As String
    Endereço = msEndereço
End Property
 
'Propriedade Salário:
Property Let Salário(d As Double)
    mdSalário = d
End Property
Property Get Salário() As Double
    Salário = mdSalário
End Property

Para cada uma das propriedades, há um procedimento Let e outro Get. Let é chamado quando se deseja atribuir um valor a uma propriedade e Get é chamado quando se deseja ler o valor de 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

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 mdSalá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 adquirir o valor da propriedade, você terá garantido que o valor que está usando 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 d > 0 Then
        mdSalário = d
    Else
        MsgBox "O valor do salário deve ser maior que 0!", vbCritical
    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 s As String
    
    s = s & "Nome: " & Nome & vbCrLf
    s = s & "Data atual: " & Date & vbCrLf
    s = s & vbNewLine
    s = s & "Salário Mensal: " & Salário & vbCrLf
    s = s & "Salário Anual: " & SalárioAnual & vbCrLf
    MsgBox s, 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 msEndereço As String
Private mdDó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 Let Endereço(s As String)
    msEndereço = s
End Property
Property Get Endereço() As String
    Endereço = msEndereço
End Property
 
Property Let Dólares(d As Double)
    mdDólares = d
End Property
Property Get Dólares() As Double
    Dólares = mdDó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 False para o teste. Nesse exemplo, o teste If oCarro Is Nothing Then... sempre irá passar.

Para fazer download do arquivo de exemplo deste artigo, clique aqui.

Publicado em Tutoriais | Com a tag , , , , , , | Deixar um comentário