独自のクラスに引数を直接渡すオブジェクトをどのように構築できますか?
このようなもの:
Dim this_employee as Employee
Set this_employee = new Employee(name:="Johnny", age:=69)
これを行うことができないのは非常に迷惑であり、これを回避するための汚い解決策になります。
ここに私が最近使用している小さなトリックがあり、良い結果をもたらします。 VBAと頻繁に戦わなければならない人たちと共有したいと思います。
1 .-各カスタムクラスにパブリック開始サブルーチンを実装します。すべてのクラスでInitiatePropertiesと呼びます。このメソッドは、コンストラクターに送信する引数を受け入れる必要があります。
2 .- factoryというモジュールを作成し、「Create」という単語にクラスと同じ名前を追加し、コンストラクターが必要とする同じ入力引数を使用してパブリック関数を作成します。この関数は、クラスをインスタンス化し、ポイント(1)で説明した開始サブルーチンを呼び出して、受け取った引数を渡す必要があります。最後に、インスタンス化され開始されたメソッドを返しました。
例:
カスタムクラスEmployeeがあるとします。前の例のように、名前と年齢でインスタンス化する必要があります。
これは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が指摘したように、コンストラクター関数でローカル変数を作成することを避けることにより、terser構文で同じことを行うことができます。たとえば、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
メンバーを呼び出すクラスごとに1つ(または複数)constructorを含むInit
モジュールを1つ使用します。
たとえば、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
このアプローチの優れた点の1つは、式内でファクトリ関数を簡単に使用できることです。たとえば、次のようなことができます。
D = Distance(NewPoint(10, 10), NewPoint(20, 20)
または:
D = NewPoint(10, 10).Distance(NewPoint(20, 20))
それはきれいです:ファクトリはほとんど何もせず、すべてのオブジェクトにわたって一貫して行います。作成と、各creatorでのInit
呼び出しだけです。
そして、それはかなりオブジェクト指向です:Init
関数はオブジェクト内で定義されます。
編集
これにより静的メソッドを作成できることを付け加えるのを忘れました。たとえば、次のようなことができます(パラメーターをオプションにした後):
NewLine.DeleteAllLinesShorterThan 10
残念ながら、オブジェクトの新しいインスタンスは毎回作成されるため、実行後に静的変数は失われます。この擬似静的メソッドで使用される行のコレクションおよびその他の静的変数は、モジュールで定義する必要があります。
クラスモジュールをエクスポートしてメモ帳でファイルを開くと、上部近くに隠された属性の束があります(VBEはそれらを表示せず、機能のほとんどをTweakに公開しません)。それらの1つは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
はデフォルトインスタンスで機能しています。つまり、typeのメンバーと見なされ、デフォルトインスタンスからのみ呼び出されます。
問題は、これも完全に合法であることです:
Dim emplFactory As New Employee
Dim empl As Employee
Set empl = emplFactory.Create("Johnny", 69)
これは、ややこしいです。これは、混乱を招くAPIができたからです。 '@Description
アノテーション/ VB_Description
属性を使用して使用を文書化できますが、Rubberduckがなければ、呼び出しサイトでその情報を表示するエディターには何もありません。
また、Property Let
メンバーにアクセスできるため、Employee
インスタンスはmutableです。
empl.Name = "Booba" ' Johnny no more!
コツは、クラスにinterfaceを実装させて、公開する必要があるものだけを公開することです:
[IEmployee
クラス]
Option Explicit
Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property
そして今、Employee
implementIEmployee
を作成します-最終的なクラスは次のようになります:
[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
Create
メソッドがインターフェイスを返し、インターフェイスdoes n'tがProperty Let
メンバーを公開することに注意してください。呼び出しコードは次のようになります。
Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)
また、クライアントコードはインターフェイスに対して記述されているため、empl
が公開するメンバーはIEmployee
インターフェイスで定義されたメンバーだけです。つまり、Create
メソッド、Self
ゲッター、Property Let
ミューテーター:したがって、「具象」Employee
クラスを使用する代わりに、残りのコードは「抽象」IEmployee
インターフェースを使用して、不変のポリモーフィックオブジェクトを使用できます。
トリックを使用する
Attribute VB_PredeclaredId = True
私は別のよりコンパクトな方法を見つけました:
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
ご覧のように、クラスのプライベートメンバーの作成と設定(initなど)の両方を行うためにNew_コンストラクターが呼び出されるのは、非静的インスタンスで呼び出された場合、プライベートメンバーを再初期化するためです。ただし、フラグを設定することで回避できます。