EDIT Mat's Mug has the best approach in going for an Interface and an add-in, but if you constrain the requirements to a self-contained solution, and you assume the user of the class is unfamiliar with interfaces, then you can have everything in a single class.
Your predeclared instance has the private fields and property gets to facilitate passing the variables to new instances. I've added a State
property (replacing your IsMaking
property) with a Friend
scope (should the class end up in an add-in), and I've used VBA's ability to hide an emum member by prefixing it with an underscore, to obfuscate the values that State
could otherwise be (i.e. Consumers of the class don't need to know what State
does, and only know that State
can be Ready
.
Your predeclared instance has the private fields and property gets to facilitate passing the variables to new instances. I've added a State
property (replacing your IsMaking
property) with a Friend
scope (should the class end up in an add-in), and I've used VBA's ability to hide an emum member by prefixing it with an underscore, to obfuscate the values that State
could otherwise be (i.e. Consumers of the class don't need to know what State
does, and only know that State
can be Ready
.
EDIT Mat's Mug has the best approach in going for an Interface and an add-in, but if you constrain the requirements to a self-contained solution, and you assume the user of the class is unfamiliar with interfaces, then you can have everything in a single class.
Your predeclared instance has the private fields and property gets to facilitate passing the variables to new instances. I've added a State
property (replacing your IsMaking
property) with a Friend
scope (should the class end up in an add-in), and I've used VBA's ability to hide an emum member by prefixing it with an underscore, to obfuscate the values that State
could otherwise be (i.e. Consumers of the class don't need to know what State
does, and only know that State
can be Ready
.
Your predeclared instance has the private fields and property gets to facilitate passing the variables to new instances. I've added a State
property (replacing your IsMaking
property) with a Friend
scope (should the class end up in an add-in), and I've used VBA's ability to hide an emum member by prefixing it with an underscore, to obfuscate the values that State
could otherwise be (i.e. Consumers of the class don't need to know what State
does, and only know that State
can be Ready
.
Setting up some Enums
As @Mat'sMug pointed out, the error values could use their own Enum:
Public Enum pointError
errNotCreatable = vbObjectError + 100
errOnlyCallableFromDefault = vbObjectError + 101
End Enum
And, I'll use an Enum for setting the state of the predeclareed instance. It's either ready, or it's Initializing a new Point. Note the use of a leading _
to make the non-default enum member hidden, and the use of square-brackets to make the syntax valid.
Public Enum pointState
Ready = 0
[_InitializingNewPoint] = 1
End Enum
Making fields more friendly
Then, again as per Mat'sMug's suggestion, I'm using a private type to store fields:
Private Type TPoint
X As Double
Y As Double
State As pointState
End Type
Private this As TPoint
Hijacking the predeclared instance's fields
In the factory method Create
, I temporarily set the state and X/Y fields of the default instance, and then in the Class_Initialize
event of the new instance, I read those fields though the default instance's getters. As such, I've relaxed the conditions around being able to access the default instance's getters, but this could easily be re-added. The default Point
will always have X and Y's getters return 0, in much the same way that the default Integer
will always have default value 0.
Public Function Create(X As Double, Y As Double) As Point
If Me Is Point Then
this.State = pointState.[_InitializingNewPoint]
'Hijack the default instance's X and Y properties
this.X = X
this.Y = Y
Set Create = New Point
'Restore the default instance's X and Y properties
this.X = 0
this.Y = 0
this.State = pointState.Ready
Else
Err.Raise pointError.errOnlyCallableFromDefault, "Point", "Invalid instance use of Create - Use Point.Create to instatiate new Point objects."
End If
End Function
Complete Code
This is a pre-declared class. I require that new instances of Point be created from the default instance, and only the default instance of Point (which means that the default instance could do useful things like tracking the number of Point instances), but it isn't much work to facilitate any instance of Point being a valid factory.
Option Explicit
Public Enum pointError
errNotCreatable = vbObjectError + 100
errOnlyCallableFromDefault = vbObjectError + 101
End Enum
Public Enum pointState
Ready = 0
[_InitializingNewPoint] = 1
End Enum
Private Type TPoint
X As Double
Y As Double
State As pointState
End Type
Private this As TPoint
Private Sub Class_Initialize()
If Not Me Is Point Then
If Point.State = pointState.[_InitializingNewPoint] Then
this.X = Point.X
this.Y = Point.Y
Else
Err.Raise pointError.errNotCreatable, "Point", "Invalid use of New Keyword - Use Point.Create to instatiate new Point objects."
End If
End If
End Sub
Public Property Get X()
X = this.X
End Property
Public Property Get Y()
Y = this.Y
End Property
Friend Property Get State() As pointState
State = this.State
End Property
Public Function Create(X As Double, Y As Double) As Point
If Me Is Point Then
this.State = pointState.[_InitializingNewPoint]
'Hijack the default instance's X and Y properties
this.X = X
this.Y = Y
Set Create = New Point
'Restore the default instance's X and Y properties
this.X = 0
this.Y = 0
this.State = pointState.Ready
Else
Err.Raise pointError.errOnlyCallableFromDefault, "Point", "Invalid instance use of Create - Use Point.Create to instatiate new Point objects."
End If
End Function
Usage:
Dim p As Point
Set p = Point.Create(1, 2)
Debug.Print p.X, p.Y
Dim o As New Point
Debug.Print ObjPtr(o) 'Throws: Invalid use of New Keyword - Use Point.Create to instatiate new Point objects.
Dim q As Point
Set q = New Point 'Throws: Invalid use of New Keyword - Use Point.Create to instatiate new Point objects.
Dim r As Point
Set r = p.Create(1, 2) 'Throws: Invalid instance use of Create - Use Point.Create to instatiate new Point objects.