Skip to content

Latest commit

 

History

History
508 lines (402 loc) · 19.6 KB

File metadata and controls

508 lines (402 loc) · 19.6 KB

Setup

Setup is quick and painless with handy templates. Simply fill out the TODOs and paste the result in your module!

Consolidated

To consolidate everything within your existing module, fill out SnippetTemplate.bas and paste into your module. Then paste Snippet.bas alongside it.

Outsourced

To outsource the SOb framework to a single external dependency, fill out SObTemplate.bas and paste into your module. Then instruct your end user to import the SOb.bas module, which you may reference as a submodule in your repo.

Warning

By outsourcing, you reduce your security against tampering! The "encryption" is no longer scoped to your module, so others can overwrite1 your "private" fields.

Template

Fill out either template according to these steps:

  1. TODO: Replace every * with the ("class") name you desire for your SOb. A simple Find & Replace should suffice.

    So if you name your SOb something like "Foo", this should yield a FOO_CLS constant atop your module…

    -	Private Const   *_CLS As String = "*"
    +	Private Const FOO_CLS As String = "Foo"

    …along with a Foo__Field enumeration below…

    -	Private Enum   *__Field
    +	Private Enum Foo__Field
    		FieldOne    ' The 1st field.
    		FieldTwo    ' The 2nd field.
    		FieldThree  ' The 3rd field.
    		' ...
    	End Enum

    …followed by procedures of the form Foo_…()

    -	Public Function   *_Format(ByRef * As Object, _
    +	Public Function Foo_Format(ByRef * As Object, _
    		Optional ByVal depth = 1, _
    		Optional ByVal plain As Boolean = False, _
    		Optional ByVal pointer As Boolean = False, _
    		Optional ByVal preview As Boolean = False, _
    		Optional ByVal indent As String = VBA.vbTab, _
    		Optional ByVal orphan As Boolean = True _
    	) As String
    -		  *_Format = Obj_Format(*, _
    +		Foo_Format = Obj_Format(*, _
    			 _
    			 _
    			 _
    			depth := depth, _
    			plain := plain, _
    			pointer := pointer, _
    			preview := preview, _
    			indent := indent, _
    			orphan := orphan _
    		)
    	End Function

    …with a foo argument.

    -	Public Function Foo_Format(ByRef *   As Object, _
    +	Public Function Foo_Format(ByRef foo As Object, _
    		Optional ByVal depth = 1, _
    		Optional ByVal plain As Boolean = False, _
    		Optional ByVal pointer As Boolean = False, _
    		Optional ByVal preview As Boolean = False, _
    		Optional ByVal indent As String = VBA.vbTab, _
    		Optional ByVal orphan As Boolean = True _
    	) As String
    -		Foo_Format = Obj_Format(*  , _
    +		Foo_Format = Obj_Format(foo, _
    			 _
    			 _
    			 _
    			depth := depth, _
    			plain := plain, _
    			pointer := pointer, _
    			preview := preview, _
    			indent := indent, _
    			orphan := orphan _
    		)
    	End Function
  2. TODO: Enumerate the fields you desire for your SOb. Currently there are three placeholders for these fields: FieldOne and FieldTwo and FieldThree. Feel free to Find & Replace these, and to append further fields of your own.

    	Private Enum Foo__Field
    -		FieldOne    ' The 1st field.
    +		Bar         ' The 1st field.
    -		FieldTwo    ' The 2nd field.
    +		Baz         ' The 2nd field.
    -		FieldThree  ' The 3rd field.
    +		Qux         ' The 3rd field.
    	End Enum
    	Public Property Get Foo_FieldOne(ByRef foo As Object) As Boolean
    -		Let Foo_FieldOne = Obj_Field(foo, Foo__Field.FieldOne)
    +		Let Foo_FieldOne = Obj_Field(foo, Foo__Field.Bar     )
    	End Property
    	
    	Public Property Let Foo_FieldOne(ByRef foo As Object, ByVal val As Boolean)
    -		Let Obj_Field(foo, Foo__Field.FieldOne) = val
    +		Let Obj_Field(foo, Foo__Field.Bar     ) = val
    	End Property

    This way, you can specify a field to (say) Obj_Field() by simply selecting it from the dropdown for Foo__Field.….

  3. TODO: Implement accessors for your Foo__Field.… fields. Each should be a Property of the form Foo_…(ByRef foo As Object), and you may restrict it to internal usage via the Private statement.

    -	Public Property Get Foo_FieldOne(ByRef foo As Object) As Boolean
    +	Public Property Get Foo_Bar     (ByRef foo As Object) As Boolean
    -		Let Foo_FieldOne = Obj_Field(foo, Foo__Field.Bar)
    +		Let Foo_Bar      = Obj_Field(foo, Foo__Field.Bar)
    	End Property
    	
    -	Public Property Let Foo_FieldOne(ByRef foo As Object, ByVal val As Boolean)
    +	Public Property Let Foo_Bar     (ByRef foo As Object, ByVal val As Boolean)
    		Let Obj_Field(foo, Foo__Field.Bar) = val
    	End Property
    	Private Sub Foo_Initialize(ByRef foo As Object)
    		Const CLS_NAME As String = FOO_CLS
    		
    		Obj_Initialize foo, CLS_NAME
    		
    		
    		If Not Obj_HasField(foo, Foo__Field.Bar) Then
    			Dim f1 As Boolean: ' Let f1 = ...
    -			Let Foo_FieldOne(foo) = f1
    +			Let Foo_Bar     (foo) = f1
    		End If
    		
    		If Not Obj_HasField(foo, Foo__Field.Baz) Then
    			Dim f2 As Range: ' Set f2 = ...
    -			Set Foo_FieldTwo(foo) = f2
    +			Set Foo_Baz     (foo) = f2
    		End If
    		
    		If Not Obj_HasField(foo, Foo__Field.Qux) Then
    			Dim f3 As Variant: ' Assign f3, ...
    -			Assign Foo_FieldThree(foo), f3
    +			Assign Foo_Qux       (foo), f3
    		End If
    	End Sub

    Simply wrap Obj_Get() with a Property Get to retrieve a field…

    	Property Get Foo_Bar(ByRef foo As Object) As Boolean
    		Obj_Get Foo_Bar, foo, Foo__Field.Bar
    	End Property

    …but implement a Property Let to update a scalar field

    	Property Let Foo_Bar(ByRef foo As Object, ByVal val As Boolean)
    		Let Foo_Bar = val
    	End Property

    …or a Property Set to update an objective field.

    	Property Set Foo_Baz(ByRef foo As Object, ByRef val As Range)
    		Set Foo_Baz = val
    	End Property
  4. TODO: Initialize the values for your fields, within Foo_Initialize(). Use Obj_HasField() to test whether a field exists, and when it does not, use your accessor to set its initial value2.

    	Private Sub Foo_Initialize(ByRef foo As Object)
    		Const CLS_NAME As String = FOO_CLS
    		
    		Obj_Initialize foo, CLS_NAME
    		
    		
    		If Not Obj_HasField(foo, Foo__Field.Bar) Then
    -			Dim f1  As Boolean: ' Let f1  = ...
    +			Dim bar As Boolean:   Let bar = False
    -			Let Foo_Bar(foo) = f1
    +			Let Foo_Bar(foo) = bar
    		End If
    		
    		If Not Obj_HasField(foo, Foo__Field.Baz) Then
    -			Dim f2  As Range: ' Set f2  = ...
    +			Dim baz As Rangę:   Set baz = Nothing
    -			Set Foo_Baz(foo) = f2
    +			Set Foo_Baz(foo) = baz
    		End If
    		
    		If Not Obj_HasField(foo, Foo__Field.Qux) Then
    -			Dim f3  As Variant: ' Assign f3 , ...
    +			Dim qux As Variant:   Assign qux, Empty
    -			Assign Foo_Qux(foo), f3
    +			Assign Foo_Qux(foo), qux
    		End If
    	End Sub
  5. TODO: List all your Foo__Field.… fields in the Array(…) call, within IsFoo().

    	Public Function IsFoo(ByRef foo As Variant, _
    		Optional ByVal strict As Boolean = False _
    	) As Boolean
    		Const CLS_NAME As String = FOO_CLS
    		
    		
    		' ### Class and Fields ###
    		
    		' Ensure an accurate class with its proper set of fields.
    		IsFoo = IsObj(x, class := CLS_NAME, strict := strict, fields := Array( _
    +			Foo__Field.Bar, _
    +			Foo__Field.Baz, _
    +			Foo__Field.Qux _
    		))
    		If Not IsFoo Then Exit Function
    		
    		
    		' Return the result in lieu of errors.
    		Exit Function
    	End Function

    This way, IsFoo() checks that a "Foo" object has all its fields.

  6. TODO: Using your accessors, assign each field from obj to its corresponding field in AsFoo, within AsFoo().

    	Public Function AsFoo(ByRef x As Variant) As Object
    		' Cast the input to a (generic) simulated object...
    		Dim obj As Object: Set obj = AsObj(x)
    		
    		' ...and extract its fields into a new "Foo" object.
    		Set AsFoo = New_Foo()
    		
    +		Let Foo_Bar(AsFoo) = Foo_Bar(obj)
    +		Set Foo_Baz(AsFoo) = Foo_Baz(obj)
    +		Assign Foo_Qux(AsFoo), Foo_Qux(obj)
    	End Function

    This way, AsFoo() can cast any input (x) to a "Foo" object, by extracting fields from the former into the latter.

  7. TODO: Create any summary or details you wish, to visually represent your object within Foo_Format().

    	Public Function Foo_Format(ByRef foo As Object, _
    		Optional ByVal depth = 1, _
    		Optional ByVal plain As Boolean = False, _
    		Optional ByVal pointer As Boolean = False, _
    		Optional ByVal preview As Boolean = False, _
    		Optional ByVal indent As String = VBA.vbTab, _
    		Optional ByVal orphan As Boolean = True _
    	) As String
    +		Dim summary As String: summary = "..."
    +		Dim details As String: details = "..." & vbNewLine & "..." & vbNewLine & "..."
    		
    		' Adjust settings to your satisfaction.
    		Foo_Format = Obj_Format(foo, _
    			 _
    			 _
    			 _
    			depth := depth, _
    			plain := plain, _
    			pointer := pointer, _
    			preview := preview, _
    			indent := indent, _
    			orphan := orphan _
    		)
    	End Function

    The SOb framework automatically formats these for you: summaries display on a single line…

    <Foo[...]>
    

    …and details display across multiple lines:

    <Foo: {
    	...
    	...
    	...
    }>
    
  8. TODO: Pass any summary or details to Obj_Format(), along with all arguments from Foo_Format().

    	Public Function Foo_Format(ByRef foo As Object, _
    		Optional ByVal depth = 1, _
    		Optional ByVal plain As Boolean = False, _
    		Optional ByVal pointer As Boolean = False, _
    		Optional ByVal preview As Boolean = False, _
    		Optional ByVal indent As String = VBA.vbTab, _
    		Optional ByVal orphan As Boolean = True _
    	) As String
    		Dim summary As String: summary = "..."
    		Dim details As String: details = "..." & vbNewLine & "..." & vbNewLine & "..."
       	
    		' Adjust settings to your satisfaction.
    		Foo_Format = Obj_Format(foo, _
    +			summary := summary, _
    +			details := details, _
    			depth := depth, _
    			plain := plain, _
    			pointer := pointer, _
    			preview := preview, _
    			indent := indent, _
    			orphan := orphan _
    		)
    	End Function

    This way, others can apply various settings when printing your "Foo" object, including developers who wish to build their own SObs upon "Foo".

You may optionally enhance "Foo" with further steps:

  1. TODO: Implement any methods you desire, which operate on your "Foo" object. Each should be a Function or Subroutine of the form Foo_…(ByRef foo As Object, …) where foo is followed by any arguments needed by the method. You may restrict it to internal usage via the Private statement.

    	' ###################
    	' ## Foo | Methods ##
    	' ###################
    	
    +	Public Function Foo_Corge(ByRef foo As Object, _
    +		arg1 As Integer, _
    +		Optional arg2 As Integer = 0 _
    +	) As Integer
    +		Let Foo_Corge = arg1 + arg2 + Foo_Bar(foo) + Foo_Baz(foo).Cells.Count
    +	End Function
  2. TODO: Call all your field accessors like Foo_FieldOne(), in the Obj_Check … call within IsFoo().

    	Public Function IsFoo(ByRef x As Variant, _
    		Optional ByVal strict As Boolean = False _
    	) As Boolean
    		Const CLS_NAME As String = FOO_CLS
    		
    		
    		' ### Class and Fields ###
    		
    		' Ensure an accurate class with its proper set of fields.
    		IsFoo = IsObj(x, class := CLS_NAME, strict := strict, fields := Array( _
    			Foo__Field.Bar, _
    			Foo__Field.Baz, _
    			Foo__Field.Qux _
    		))
    		If Not IsFoo Then Exit Function
    		
    		
    		' ### Accessors ###
    		
    		' Treat as an object moving forward.
    		Dim obj As Object: Set obj = x
    		
    		' Ensure the field accessors all work.
    		On Error GoTo CHECK_ERROR
    		
    		If IsFoo Then Obj_Check _
    +			Foo_FieldOne(obj), _
    +			Foo_FieldTwo(obj), _
    +			Foo_FieldThree(obj)
    		
    		On Error GoTo 0
    		If Not IsFoo Then Exit Function
    		
    		
    		' Return the result in lieu of errors.
    		Exit Function
    		
    	' Handle inaccessibility.
    	CHECK_ERROR:
    		IsFoo = Obj_CheckError()
    	End Function

    This way, IsFoo() also validates that the "Foo" fields are of the expected type, and so forth.

  3. TODO: Specify which validation errors (like type) you wish to catch, via arguments to the Obj_CheckError(…) call within IsFoo(). See Obj_CheckError() for details.

    	' Handle inaccessibility.
    	CHECK_ERROR:
    -		IsFoo = Obj_CheckError()
    +		IsFoo = Obj_CheckError(type_ := True)
    	End Function

    This way, IsFoo() returns False when errors disqualify the input (x) as a "Foo" object, while "bubbling up" other errors for (say) improper usage.

  4. TODO: Customize any further validation you wish IsFoo() to perform. Each validation step should assign a Boolean value to IsFoo

    		IsFoo = …

    …and finish by short-circuiting when False.

    		If Not IsFoo Then Exit Function

Now you are ready to work with "Foo" objects, within your module and elsewhere!

Footnotes

  1. Others cannot typically overwrite the value of a "private" field in your SOb—though they can remove the field, which effectively resets it to an uninitialized state.

    However, if you outsource the framework from your module to the SOb module, then others can overwrite it via SOb.Obj_Field().

  2. You may specify a Range with its .Address in shortcut notation: [A1:B2].