The SOb framework lets you simulate "objects" in VBA.
With a full suite of features at your fingertips — including pretty printing — your simulated object ("SOb") mimics an object or UDT without the frustrating downsides. No matter how many SObs you need, or where you need them, this framework supports them within your existing code. No imports are needed!
Tip
See here for the latest (pre)release.
I first encountered this use case when developing GitHelp, which simulates a fielded "library" of documentation. It demanded an innovative approach, and as seasoned developers chimed in, this took on a life of its own!
Like me, you might desire several such data structures, where some fields are accessible (or not) to outside users. These structures (like UDTs) are self-contained within your module, yet (like objects) they can be used by object classes and modules alike. Ideally these other modules should still compile in the absence of yours, which should be easy for lay users to (re)install.
Unfortunately, neither objects nor UDTs achieve this outcome! For every object you include, your users must install an additional class module. And if objects "are a pain", then "UDTs are notoriously problematic".
The SOb framework addresses all these shortcomings. It builds your SOb atop a Collection, which is native to VBA across platforms (Windows and Mac). And unlike classes, your SObs carry no baggage whatsoever—you can easily set them all up within your existing module!
| Feature | Description | SOb | Object | UDT |
|---|---|---|---|---|
| Painless | Is it quick and easy for you to code? | ✓ | 1 | ✓ |
| Installable | Is it quick and easy for lay users to install your code? | ✓ | 2 | ✓ |
| Native | Is it native to VBA? | ✓ | ✓ | ✓ |
| Portable | Does it work across all platforms? | ✓ | ✓ | ✓ |
| Independent | Is it free of external dependencies? | ✓ | 3 | ✓ |
| Global | Can it be used seamlessly across other modules and classes? | ✓ 4 | ✓ | 5 |
| Compilation | Can its dependents compile in its absence? | ✓ | 6 | 7 |
| Instantiation | Can you dynamically declare new instances after design time? | ✓ | ✓ 8 | 9 |
| Placeholder | Can it be passed to a generic Variant or Object? |
✓ | ✓ 10 | 7 |
| Collectible | Can it be included within a Collection (or Dictionary)? |
✓ | ✓ 11 | 7 |
| Identity | Is its type identifiable by name, so you can distinguish it? | ✓ 12 | ✓ 13 | |
| Methods | Does it support procedures that operate on it? | ✓ 14 | ✓ 15 | 16 |
| Printing | Does it support pretty printing for visualization? | ✓ | 17 | |
| Validation | Can it validate values before they are assigned to fields? | ✓ 18 | ✓ 19 | 20 |
| Private | Can you hide certain fields (and "methods") from your user? | ✓ 21 | ✓ 21 | |
| Secure | Are its fields secure against unauthorized editing? | ✓ 22 | ✓ | 23 |
Setup is quick and painless with handy templates. Simply fill out the TODOs and paste the result in your module! See here for detailed instructions.
- To consolidate everything within your module, use
SnippetTemplate.basalongsideSnippet.bas. See here for details. - To outsource the
SObframework to a dependency, useSObTemplate.basbut importSOb.basseparately. See here for details.
Using an SOb is analogous to using an object. The SOb framework provides a backend, which lets you implement your frontend for your actual SOb.
Simply enumerate its fields (like "Bar") in the template, and you may manipulate your SOb ("Foo") as illustrated below.
Private Enum Foo__Fields
Bar
' ...
End EnumSee documentation for further details and concrete examples.
| Action | Frontend | Backend | Object | UDT | |||
|---|---|---|---|---|---|---|---|
| Declaration | Dim x As Object |
Dim x As Object |
Dim x As Foo |
Dim x As Foo |
|||
| Instantiation | Set x = New_Foo() |
Set x = New_Obj("Foo") |
Set x = New Foo |
||||
| Reading | Foo_Bar(x) |
Obj_Field(x, Bar) |
x.Bar |
x.Bar |
|||
| Writing | Foo_Bar(x) = 1 |
Obj_Field(x, Bar) = 1 |
x.Bar = 1 |
x.Bar = 1 |
|||
| Invocation | Foo_Fun(x, …) |
x.Fun(…) |
Here are all the features provided by SOb for developers. To avoid confusing your users, the SOb module hides its own functions from Excel, via Option Private.
Describe the SOb module itself.
MOD_NAME: The name (String) of the module.MOD_VERSION: Its current version (String).MOD_REPO: The URL (String) to its repository.
"Declare" a new SOb.
New_Obj(): Returns an initialized SOb (Object).Obj_Initialize(): Initializes a genericObjectas an SOb.
Ascertain the "type" of an SOb…
Obj_Class(): Retrieve the simulated "class" (String) of an SOb.IsObj(): Test (Boolean) if something is an SOb.
…and manipulate that type.
AsObj(): Cast something as an SOb (Object).
Access simulated "fields" in an SOb…
Obj_Field(): Read (Get) and write (LetorSet) the field as aProperty.Obj_Get(): A delegate ofProperty Getwith protection against missing fields.
…along with metadata about such fields.
Obj_FieldCount(): The (maximum) count (Long) of simulated fields in an SOb.Obj_HasField(): Test (Boolean) if an SOb has a certain field.Obj_HasFields(): Test (Boolean) if an SOb has an entire set of fields, wrapped in anArray()…Obj_HasFields0(): …or entered manually.
Validate SObs within advanced implementations of Is*().
Obj_Check(): Call your accessors without assignment, merely to test (say) their type integrity.Obj_CheckError(): Test (Boolean) if certain errors (like type) invalidate the check, but propagate any other errors.
Textually visualize the entire SOb…
Obj_Print(): Print (String) an SOb to the console with automatic formatting.Obj_Print0(): Print something (String) verbatim to the console.Obj_Format(): Automatically format (String) an SOb for printing.
…or specifically its fields in detail.
Obj_FormatFields(): Automatically format (String) a set of simulated fields, wrapped in anArray()…Obj_FormatFields0(): …or entered manually with default settings.
Perform broadly useful (Public) tasks via the SOb module…
Assign(): Assign any value (scalar or objective) to a variable (by reference).Txt_Indent(): Indent (String) some lines of text.
…along with further (Private) tasks via an SOb snippet in your own module.
Clx_Has(): Test (Boolean) if aCollectioncontains an item.Clx_Get(): Safely retrieve any item (Variant) from aCollection.Clx_Set(): Set the value of an item in aCollection.Arr_Length(): Get the length (Long) of an array.Err_Raise(): Raise an error object directly.Txt_Contains(): Test (Boolean) if text contains a substring.
Footnotes
-
"Classes are a pain" to develop. ↩
-
To avoid burdening users with prohibitive setup, developers have often resorted to dubious hacks! ↩
-
For every object you include, your users must install an additional class module. ↩
-
A class module may call procedures from standard modules, like your own module or even the
SObmodule. ↩ -
UDTs are restrictively siloed between classes and modules.
-
Their absence can derail compilation, unless other modules inefficiently resort to late-binding. ↩
-
Not unless you reference the UDT in a type library. ↩ ↩2 ↩3 ↩4
-
You may declare new instances of objects at runtime, using the
Newkeyword… ↩ -
…but you may not declare new instances of UDTs. ↩
-
You cannot pass them to placeholders like
VariantorObject… ↩ -
…nor can you include them within a
Collectionor (on Windows) aDictionary. ↩ -
Via
Obj_Class()andIsObj(). ↩ -
Via the
TypeName()function or theTypeOfoperator. ↩ -
Technically these "methods" are simply modular procedures of the form
SOb_Method(sob, …), where thesobis passed by reference. ↩ ↩2 -
…but UDTs do not support methods and "cannot carry out actions".
Technically, you could imitate an SOb and implement "methods"14 of the form
UDT_Method(udt, …), where theudtis passed by reference. ↩ -
Unlike .NET and other languages, VBA does not implement a prototypical
.ToString()method for objects. ↩ -
The accessors for your SOb are
Propertyprocedures, in which you may validate input19 before assigning it to the field. ↩ -
Objects use
Propertyprocedures to validate values for fields… ↩ ↩2 -
…but UDTs have no mechanism for validation. ↩
-
Via the
Privatekeyword for properties (and procedures). ↩ ↩2 -
Its fields are "encrypted" against the more insidious tampering. 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
SObmodule, then others can overwrite it viaSOb.Obj_Field(). ↩ -
Their fields are still vulnerable to editing. ↩