Passar argumentos ao construtor no VBA

Como você pode construir objects passando argumentos diretamente para suas próprias classs?

Algo assim:

Dim this_employee as Employee Set this_employee = new Employee(name:="Johnny", age:=69) 

Não ser capaz de fazer isso é muito chato, e você acaba com soluções sujas para resolver isso.

Aqui está um pequeno truque que estou usando ultimamente e traz bons resultados. Eu gostaria de compartilhar com aqueles que têm que lutar frequentemente com o VBA.

1.- Implemente uma sub-rotina de iniciação pública em cada uma das suas classs personalizadas. Eu chamo de InitiateProperties em todas as minhas aulas. Este método tem que aceitar os argumentos que você gostaria de enviar para o construtor.

2.- Crie um módulo chamado factory, e crie uma function pública com a palavra “Create” mais o mesmo nome da class, e os mesmos argumentos de input que o construtor precisa. Esta function tem que instanciar sua class e chamar a sub-rotina de iniciação explicada no ponto (1), passando os argumentos recebidos. Finalmente retornou o método instanciado e iniciado.

Exemplo:

Digamos que temos a class personalizada Employee. Como o exemplo anterior, tem que ser instanciado com nome e idade.

Este é o método InitiateProperties. m_name e m_age são nossas propriedades privadas a serem definidas.

 Public Sub InitiateProperties(name as String, age as Integer) m_name = name m_age = age End Sub 

E agora no módulo de fábrica:

 Public Function CreateEmployee(name as String, age as Integer) as Employee Dim employee_obj As Employee Set employee_obj = new Employee employee_obj.InitiateProperties name:=name, age:=age set CreateEmployee = employee_obj End Function 

E finalmente, quando você deseja instanciar um funcionário

 Dim this_employee as Employee Set this_employee = factory.CreateEmployee(name:="Johnny", age:=89) 

Especialmente útil quando você tem várias classs. Basta colocar uma function para cada na fábrica do módulo e instanciar apenas chamando factory.CreateClassA (arguments) , factory.CreateClassB (other_arguments) , etc.

EDITAR

Como Stenci apontou, você pode fazer a mesma coisa com uma syntax terser, evitando criar uma variável local nas funções do construtor. Por exemplo, a function CreateEmployee poderia ser escrita assim:

 Public Function CreateEmployee(name as String, age as Integer) as Employee Set CreateEmployee = new Employee CreateEmployee.InitiateProperties name:=name, age:=age End Function 

O que é melhor.

Eu uso um módulo de Factory que contém um (ou mais) construtor por class que chama o membro de Init de cada class.

Por exemplo, uma class Point :

 Class Point Private X, Y Sub Init(X, Y) Me.X = X Me.Y = Y End Sub 

Uma class de Line

 Class Line Private P1, P2 Sub Init(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2) If P1 Is Nothing Then Set Me.P1 = NewPoint(X1, Y1) Set Me.P2 = NewPoint(X2, Y2) Else Set Me.P1 = P1 Set Me.P2 = P2 End If End Sub 

E um módulo de Factory :

 Module Factory Function NewPoint(X, Y) Set NewPoint = New Point NewPoint.Init X, Y End Function Function NewLine(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2) Set NewLine = New Line NewLine.Init P1, P2, X1, Y1, X2, Y2 End Function Function NewLinePt(P1, P2) Set NewLinePt = New Line NewLinePt.Init P1:=P1, P2:=P2 End Function Function NewLineXY(X1, Y1, X2, Y2) Set NewLineXY = New Line NewLineXY.Init X1:=X1, Y1:=Y1, X2:=X2, Y2:=Y2 End Function 

Um aspecto interessante dessa abordagem é que facilita o uso das funções de fábrica dentro das expressões. Por exemplo, é possível fazer algo como:

 D = Distance(NewPoint(10, 10), NewPoint(20, 20) 

ou:

 D = NewPoint(10, 10).Distance(NewPoint(20, 20)) 

É limpo: a fábrica faz muito pouco e faz isso de forma consistente em todos os objects, apenas a criação e uma chamada de Init em cada criador .

E é bastante orientada a objects: as funções de Init são definidas dentro dos objects.

EDITAR

Eu esqueci de acrescentar que isso me permite criar methods estáticos. Por exemplo eu posso fazer algo como (depois de fazer os parâmetros opcionais):

 NewLine.DeleteAllLinesShorterThan 10 

Infelizmente, uma nova instância do object é criada todas as vezes, portanto, qualquer variável estática será perdida após a execução. A coleção de linhas e qualquer outra variável estática usada neste método pseudoestático deve ser definida em um módulo.

Quando você exporta um módulo de class e abre o arquivo no Bloco de notas, observa, próximo ao topo, vários atributos ocultos (o VBE não os exibe e não expõe a funcionalidade para ajustar a maioria deles). Um deles é VB_PredeclaredId :

 Attribute VB_PredeclaredId = False 

Defina como True , salve e importe novamente o módulo para o seu projeto do VBA.

Classes com um PredeclaredId têm uma “instância global” que você obtém gratuitamente – exatamente como os módulos UserForm (exporte um formulário de usuário, você verá que seu atributo predeclaredId está definido como true).

Muitas pessoas simplesmente usam a instância pré-declarada para armazenar o estado. Isso é errado – é como armazenar o estado da instância em uma class estática!

Em vez disso, você aproveita essa instância padrão para implementar seu método de fábrica:

[Classe do Employee ]

 '@PredeclaredId Option Explicit Private Type TEmployee Name As String Age As Integer End Type Private this As TEmployee Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As Employee With New Employee .Name = emplName .Age = emplAge Set Create = .Self 'returns the newly created instance End With End Function Public Property Get Self() As Employee Set Self = Me End Property Public Property Get Name() As String Name = this.Name End Property Public Property Let Name(ByVal value As String) this.Name = value End Property Public Property Get Age() As String Age = this.Age End Property Public Property Let Age(ByVal value As String) this.Age = value End Property 

Com isso, você pode fazer isso:

 Dim empl As Employee Set empl = Employee.Create("Johnny", 69) 

Employee.Create está trabalhando fora da instância padrão , ou seja, é considerado um membro do tipo e chamado apenas da instância padrão.

O problema é que isso também é perfeitamente legal:

 Dim emplFactory As New Employee Dim empl As Employee Set empl = emplFactory.Create("Johnny", 69) 

E isso é uma droga, porque agora você tem uma API confusa. Você poderia usar '@Description annotations / VB_Description para documentar o uso, mas sem o Rubberduck não há nada no editor que mostre essas informações nos sites de chamadas.

Além disso, os membros do Property Let são acessíveis, portanto, a instância do Employee é mutável :

 empl.Name = "Booba" ' Johnny no more! 

O truque é fazer com que sua class implemente uma interface que expõe apenas o que precisa ser exposto:

[ IEmployee class]

 Option Explicit Public Property Get Name() As String : End Property Public Property Get Age() As Integer : End Property 

E agora você faz o Employee implementar o IEmployee – a class final pode ser assim:

[Classe do Employee ]

 '@PredeclaredId Option Explicit Implements IEmployee Private Type TEmployee Name As String Age As Integer End Type Private this As TEmployee Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As IEmployee With New Employee .Name = emplName .Age = emplAge Set Create = .Self 'returns the newly created instance End With End Function Public Property Get Self() As IEmployee Set Self = Me End Property Public Property Get Name() As String Name = this.Name End Property Public Property Let Name(ByVal value As String) this.Name = value End Property Public Property Get Age() As String Age = this.Age End Property Public Property Let Age(ByVal value As String) this.Age = value End Property Private Property Get IEmployee_Name() As String IEmployee_Name = Name End Property Private Property Get IEmployee_Age() As Integer IEmployee_Age = Age End Property 

Observe que o método Create agora retorna a interface e a interface não expõe os membros Property Let ? Agora, o código de chamada pode ser assim:

 Dim empl As IEmployee Set empl = Employee.Create("Immutable", 42) 

E como o código do cliente é escrito na interface, os únicos membros empl IEmployee são os membros definidos pela interface IEmployee , o que significa que ele não vê o método Create , nem o Self getter, nem nenhum dos mutadores Property Let : em vez de trabalhar com a class Employee “concreta”, o restante do código pode trabalhar com a interface IEmployee “abstrata” e desfrutar de um object polimórfico imutável.

Outra abordagem

Digamos que você crie uma class clsBitcoinPublicKey

No módulo de class, crie uma sub-rotina ADICIONAL, que atue como você deseja que o construtor real se comporte. Abaixo eu nomeei ConstructorAdjunct.

 Public Sub ConstructorAdjunct(ByVal ...) ... End Sub From the calling module, you use an additional statement Dim loPublicKey AS clsBitcoinPublicKey Set loPublicKey = New clsBitcoinPublicKey Call loPublicKey.ConstructorAdjunct(...) 

A única penalidade é a chamada extra, mas a vantagem é que você pode manter tudo no módulo de class e a debugging se torna mais fácil.