program story

VBA의 생성자에 인수 전달

inputbox 2020. 10. 26. 07:57
반응형

VBA의 생성자에 인수 전달


자신의 클래스에 직접 인수를 전달하는 객체를 어떻게 구성 할 수 있습니까?

이 같은:

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

이것을 할 수 없다는 것은 매우 성가신 일이며 결국에는이 문제를 해결할 수있는 더러운 솔루션이 생깁니다.


여기 내가 최근에 사용하고 좋은 결과를 가져 오는 약간의 트릭이 있습니다. VBA와 자주 싸워야하는 분들과 함께 나누고 싶습니다.

1.- 각 사용자 정의 클래스에서 공개 시작 서브 루틴을 구현하십시오. 모든 수업에서 InitiateProperties라고 부릅니다. 이 메서드는 생성자에게 보낼 인수를 받아야합니다.

2.- factory라는 모듈을 만들고 "Create"라는 단어와 클래스와 동일한 이름, 생성자가 필요로하는 동일한 들어오는 인수를 사용하여 공용 함수를 만듭니다. 이 함수는 클래스를 인스턴스화하고 (1)에서 설명한 시작 서브 루틴을 호출하여 수신 된 인수를 전달해야합니다. 마지막으로 인스턴스화되고 시작된 메서드를 반환했습니다.

예:

사용자 정의 클래스 Employee가 있다고 가정 해 보겠습니다. 이전 예 에서처럼 is는 이름과 나이로 인스턴스화되어야합니다.

이것은 InitiateProperties 메서드입니다. m_name 및 m_age는 설정할 개인 속성입니다.

Public Sub InitiateProperties(name as String, age as Integer)

    m_name = name
    m_age = age

End Sub

이제 공장 모듈에서 :

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

마지막으로 직원을 인스턴스화하려는 경우

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

클래스가 여러 개인 경우 특히 유용합니다. 모듈 팩토리에 각각에 대한 함수를 배치하고 factory.CreateClassA (arguments) , factory.CreateClassB (other_arguments)을 호출하여 인스턴스화하십시오 .

편집하다

stenci가 지적했듯이 생성자 함수에서 지역 변수를 생성하는 것을 피함으로써 간결한 구문으로 동일한 작업을 수행 할 수 있습니다. 예를 들어 CreateEmployee 함수는 다음과 같이 작성할 수 있습니다.

Public Function CreateEmployee(name as String, age as Integer) as Employee

    Set CreateEmployee = new Employee
    CreateEmployee.InitiateProperties name:=name, age:=age

End Function

어느 쪽이 더 좋습니다.


각 클래스 멤버 를 호출하는 클래스 당 Factory하나 이상의 생성자 를 포함 하는 하나의 모듈을 사용 Init합니다.

예를 들어 Point클래스 :

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

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

그리고 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

이 접근 방식의 한 가지 좋은 점은 표현식 내에서 팩토리 함수를 쉽게 사용할 수 있다는 것입니다. 예를 들어 다음과 같이 할 수 있습니다.

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

또는:

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

깨끗합니다. 공장은 거의 일을하지 않고 모든 객체에 걸쳐 일관되게 일을합니다. 창조와 Init창조자 에 대한 한 번의 호출 뿐입니다 .

그리고 그것은 상당히 객체 지향적입니다. Init함수는 객체 내부에서 정의됩니다.

편집하다

이것은 정적 메서드를 만들 수 있다는 것을 추가하는 것을 잊었습니다. 예를 들어 다음과 같은 작업을 수행 할 수 있습니다 (매개 변수를 선택적으로 만든 후).

NewLine.DeleteAllLinesShorterThan 10

불행히도 개체의 새 인스턴스는 매번 생성되므로 실행 후 모든 정적 변수가 손실됩니다. 이 의사 정적 메서드에 사용되는 줄 모음 및 기타 정적 변수는 모듈에서 정의해야합니다.


클래스 모듈을 내보내고 메모장에서 파일을 열면 맨 위에 숨겨진 속성이 많이 있습니다 (VBE는 속성을 표시하지 않으며 대부분의 기능을 조정하는 기능도 노출하지 않습니다). 그중 하나는 다음과 VB_PredeclaredId같습니다.

Attribute VB_PredeclaredId = False

로 설정하고 True, 저장하고, 모듈을 VBA 프로젝트로 다시 가져옵니다.

가있는 클래스에는 PredeclaredId무료로 얻을 수있는 "전역 인스턴스"가 있습니다. UserForm모듈 과 똑같습니다 (사용자 양식을 내 보내면 predeclaredId 속성이 true로 설정된 것을 볼 수 있습니다).

많은 사람들이 기꺼이 미리 선언 된 인스턴스를 사용하여 상태를 저장합니다. 그것은 잘못된 것입니다. 그것은 정적 클래스에 인스턴스 상태를 저장하는 것과 같습니다!

대신 해당 기본 인스턴스를 활용하여 팩토리 메서드를 구현합니다.

[ 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

이를 통해 다음을 수행 할 수 있습니다.

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

Employee.Create기본 인스턴스 에서 작동 합니다 . 즉, 유형 의 멤버로 간주 되고 기본 인스턴스에서만 호출됩니다.

문제는 이것도 완벽하게 합법적이라는 것입니다.

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

이제 헷갈리는 API가 생겼기 때문입니다. '@Description주석 / VB_Description속성을 사용하여 사용량을 문서화 할 수 있지만 Rubberduck이 없으면 편집기에 호출 사이트에서 해당 정보를 표시하는 것이 없습니다.

게다가 Property Let멤버에 액세스 할 수 있으므로 Employee인스턴스를 변경할 수 있습니다 .

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

트릭은 클래스 가 노출되어야하는 것만 노출 하는 인터페이스구현하도록 만드는 것입니다 .

[ IEmployee클래스]

Option Explicit

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

이제 Employee 구현 IEmployee 합니다. 최종 클래스는 다음과 같습니다.

[ 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

Notice the Create method now returns the interface, and the interface doesn't expose the Property Let members? Now calling code can look like this:

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

And since the client code is written against the interface, the only members empl exposes are the members defined by the IEmployee interface, which means it doesn't see the Create method, nor the Self getter, nor any of the Property Let mutators: so instead of working with the "concrete" Employee class, the rest of the code can work with the "abstract" IEmployee interface, and enjoy an immutable, polymorphic object.


Using the trick

Attribute VB_PredeclaredId = True

I found another more compact way:

Option Explicit
Option Base 0
Option Compare Binary

Private v_cBox As ComboBox

'
' Class creaor
Public Function New_(ByRef cBox As ComboBox) As ComboBoxExt_c
  If Me Is ComboBoxExt_c Then
    Set New_ = New ComboBoxExt_c
    Call New_.New_(cBox)
  Else
    Set v_cBox = cBox
  End If
End Function

As you can see the New_ constructor is called to both create and set the private members of the class (like init) only problem is, if called on the non-static instance it will re-initialize the private member. but that can be avoided by setting a flag.


Another approach

Say you create a class clsBitcoinPublicKey

In the class module create an ADDITIONAL subroutine, that acts as you would want the real constructor to behave. Below I have named it 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(...)

The only penalty is the extra call, but the advantage is that you can keep everything in the class module, and debugging becomes easier.


Why not this way:

  1. In a class module »myClass« use Public Sub Init(myArguments) instead of Private Sub Class_Initialize()
  2. Instancing: Dim myInstance As New myClass: myInstance.Init myArguments

참고URL : https://stackoverflow.com/questions/15224113/pass-arguments-to-constructor-in-vba

반응형