VBA - Help with inheriting parent object's properties (1 Viewer)

ironfelix717

Registered User.
Local time
Today, 00:37
Joined
Sep 20, 2019
Messages
193
Greetings,

Can someone help a dummy with improving/understanding OOP in VBA...

I have a main class that has some properties, which are "inherited' by it's child classes....

Here is a sudo-description of the structure:

Main Object ParentClass
Code:
Property Let/Get ID
Property Let/Get SystemType
Property Get MyChild1 as Child1
    Set MyChild1 = new Child1
end property
Property Get MyChild2 as Child2
    Set MyChild2 = new Child2
end property

Child object Child1
Code:
Sub DoSomething()
    [*NEED TO USE ParentClass properties!*]
    Example:  Debug.print ParentClass.ID

Child object Child2
Code:
Sub DoSomething()
    [*NEED TO USE ParentClass properties!*]
   Example:   Debug.print ParentClass.ID

I want to be able to use this object as such....

Code:
Sub Test()
dim ObjParent as new ParentClass

ObjParent.ID = "1000"
ObjParent.SystemType = 1

ObjParent.MyChild1.DoSomething()
'PRINTS THE ID "1000"

ObjParent.MyChild2.DoSomething()
'PRINTS THE ID "1000"

End Sub


I hope that is helpful in explaining what I am looking for. Essentially "nested" classes with a single point of entry.
I know there are other ways of achieving something similar. Like using a 'SetParent()' method inside the children to grab the parent's properties, but this is kind of the inverse of what I am requesting.
Noob question! Sorry!

-Regards
 

MarkK

bit cruncher
Local time
Yesterday, 21:37
Joined
Mar 17, 2004
Messages
8,186
I think what you are looking for is called lazy-loading. Consider this pattern....
Code:
Private m_someClass as cForArgumentsSake

Public Property Get SomeClass as cForArgumentsSake
    If m_someClass is nothing then
        Set m_someClass = New cForArgumentsSake
        m_someClass.Load Me
    End If
    Set SomeClass = m_someClass
End Property

See how there is a private declared variable of the same type (cForArgumentsSake) as the class you want to expose. Then, in the Public Property Get you check if the private instance is initialized yet, and if not, you do that initialization. This is called lazy-loading, and it allows you to write a parent class that exposes all kinds of fancy stuff, and instantiating the parent is still not costly because none of it's child classes are actually created until requested. Brilliant. And once a child class instance is requested, and it is instantiated, then it may exist for the lifetime of the parent class.

Another common pattern is that a child property exposes a collection. DAO.Recordset, for instance, exposes a Fields collection of strongly typed DAO.Field objects. This is obviously very handy, and if you write a class, you can also expose a collection of child classes in a single collection property, which may be what you are getting at with Child1 and Child2. You can expose a child class that exposes a collection, or expose the collection directly, like...

Code:
Private Const SQL As String = _
    "SELECT OrderID FROM tOrder " & _
    "WHERE CustomerID = "

Private m_id As Long
Private m_orders As VBA.Collection

Public Property Get CustomerID as Long
    CustomerID = m_id
End Property

Public Property Get Orders as VBA.Collection
    If m_orders Is Nothing then GetOrderCollection
    Set Orders = m_orders
End Property

Private Sub GetOrderCollection
    Dim cod as cOrder

    Set m_orders = New VBA.Collection
    With CurrentDb.OpenRecordset(SQL & m_id)
        Set cod = New cOrder
        cod.Load !OrderID, Me
        m_orders.Add cod
    End With
End Sub

So that is a cCustomer class with only the members we need to also create an Orders property, which is a collection of cOrder objects that belong to that customer. Not shown is the cOrder class, which must have a Load method that takes the OrderID as a parameter, and an object reference to the parent Customer instance.

Does that help? Or is it too dense?

Anyway, I think what you are exploring here is really cool, and if you have questions, feel free to post back. Classes are very interesting, but hard to get your head around, but I think totally worth the effort.

Cheers,
Mark
 

MajP

You've got your good things, and you've got mine.
Local time
Today, 00:37
Joined
May 21, 2018
Messages
8,554
Be careful with your terminology because this will get very confusing if not.
Unfortunately there is no real subclassing in VBA custom classes. So there is really no inheritance, and you are not inheriting anything by your example. Maybe someone can argue that you can build a basic interface, but you cannot have a class inherit from a base class. I would not use the term "inheritance" because that will confuse the issue.

The term "Child" also a meaning and that is not really what you have. Normally this is referred to as a "Member Class" of a "composite class".
All you are doing is building a "Composite Class". Most classes are in fact composite classes where the class has class properties which are also classes. A fields collection of a recordset in not really a "child class" as normally understood in inheritance. A recordset is simply a composite class with a member property of "fields" that is also a class.

In your case I would only have a "Create Child" method in the parent class where you pass in the arguments to create the child. The only way to add a child (composite class) to the parent is to add it through that method. This method requires you to pass the "Parent" in so it is impossible to create a Child1,2 without first having the parent. Then in this method you can establish a pointer going from the "child" to the "parent." In this way you always create the Parent first before the child. There may be times this does not make sense if you can have these parts without the "parent", but I assume in your case it does not.

Lets assume I have class "Family" and a property (collection) "FamilyMembers" of type "People". If I add my People to the Family as a property the family class can iterate each "People" in its member class, no problem. Unless I specifically make a pointer between the members and the Family, there is not automatic pointer, if that is what you are asking. If in my Family class, I have a method

Code:
Public Sub AddFamilyMember(Name, Relation, Age,....)
  dim Person as new People
  Person.intitialize "Bob","Son","15", Me
  m_member.add(Person)
end sub

When I pass the Me that sets the ParentFamily property of the People class. In this way every "child" part gets created with a pointer to the parent.

In my People Class
Code:
Public Sub Initialize(MemberName,Relation,Age,ParentFamily as Family)
  .....
  set m_Family = ParentFamily
end sub

I do think VBA seems to have some kind of "hidden" subclassing. For example a Textbox looks to be a subclass of a Control. It inherits the properties of the Control class and has additional properties.
 

sonic8

AWF VIP
Local time
Today, 06:37
Joined
Oct 27, 2015
Messages
998
Unfortunately there is no real subclassing in VBA custom classes.
Be careful, the term subclassing in the context of VB(A) is misleading. Subclassing in VB(A) usually refers to the approach to replace the window proc of a window with your own procedure to implement some custom behavior of a window. It is not related to inheritance in an OOP context.

I do think VBA seems to have some kind of "hidden" subclassing. For example a Textbox looks to be a subclass of a Control. It inherits the properties of the Control class and has additional properties.
Every specific Access control is (extremely likely) a subclass of the generic Control class. As every form you create is a subclass of Access.Form.
However, this has little to do with VBA. Access itself is not written in VBA but in C++. So, the classes/objects in the Access object library do have real inheritance. VBA knows of this inheritance and you can use the subclass/superclass behavior of theses classes/objects in VBA. Still, you cannot use inheritance for for your own classes created in VBA.
 

ironfelix717

Registered User.
Local time
Today, 00:37
Joined
Sep 20, 2019
Messages
193
Hi all, good discussion. Thanks for chiming in.

@MarkK
We're on the same page with what you commented. My inquiry was related to the ability to access the instance of a 'parent class' properties through it's 'member objects'.

Terminology is varying but I think my point is getting across.... maybe.

@MajP
Thanks for offering your input. My choice of words for 'Child vs Parent' was for demonstration reasons, but I appreciate the correction on terminology specific to this language. It was a long day prior to this inquiry, so my question may not have been as clear as it could have been.

To clarify my original inquiry - I was trying to figure out how to access an instance of the 'composite' class' (parent) properties within each of it's 'member classes' without the need to explicitly expose a method to pass the composite (parent) instance. This is a trivial request but one I find to be interesting if it were possible. It eliminates one less line of code at the caller level, but isn't a big deal.

So i did remedy this by including a 'SetParent' routine in each member class that requires the composite (parent) object's instance.



Thanks - will keep unsolved until all discussions are had. Good topic!
 

MajP

You've got your good things, and you've got mine.
Local time
Today, 00:37
Joined
May 21, 2018
Messages
8,554
Unfortunately a the members of a Nested/Composite class have no innate knowledge of the "container" class in which it reside unless you put in the hooks to do that. I think @MarkK and I are suggesting the same thing, in slightly different implementations. You just have to code the relationship. IMO this is something that is pretty common to have to do.

Often there are certain constructs when a member property can only exist conceptually if the "container" exists. In other words your code is never going to have the need to work with those properties by themselves as discrete objects.
Lets say your parent/container object is a System and one of property objects are Software Vulnerabilities. A software vulnerability object has no meaning unless associated with a system. So when you create the Software Vulnerability you add a pointer to the parent/container. Now you can go both ways. The System has a property which is a collection of Software Vulnerabilities. But if you reference a vulnerability you can know what system it belongs to.
 

MarkK

bit cruncher
Local time
Yesterday, 21:37
Joined
Mar 17, 2004
Messages
8,186
You can have complete two-way communication between a "parent" container class, and any number of "child" exposed class instances, even if the children are all in a collection. First, consider a cOrder object like...
Code:
Private WithEvents m_parent as cCustomer
private m_data as cData
private m_orderID as long

Public Function Load(OrderID as long, Optional Parent as cCustomer) as cOrder
'   This is the cOrder constuctor, and it receives the OrderID, and optionally the parent Customer
    m_orderID = OrderID
    Set m_parent = Parent  ' now this child instance can handle "parent" events
    Set Load = Me
End Function

Public Property Get DataAccessClass as cData
    Const SQL as string = "SELECT * FROM tOrder WHERE OrderID = "
    If m_data is Nothing then m_data = NewDataAccessClass(SQL & m_OrderID)
    Set DataAccessClass = m_data
End Property

Public Property Get OrderID as Long
    OrderID = m_orderID
End Property

Public Property Get CustomerName as string
    CustomerName = Me.DataAccessClass.Fields("CustomerName")
End Property

Private m_parent_PaidOrder(OrderID as Long, Handled as Boolean)
'   This is an event handler for the cCustomer.PaidOrder event
    If Not Handled Then
        If Me.OrderID = OrderID Then
            ' the event is not handled, and this is the order the customer just paid
            DataAccessClass.Fields("IsPaid") = True  ' so we get our data class to update the table
            ' and we declare the event handled, so other consumers won't bother with it
            Handled = True
        End If
    End If
End Sub
Then consider a cCustomer class like...
Code:
Public Event PaidOrder(OrderID as Long, Handled as Boolean)

Private m_id As Long
Private m_orders As VBA.Collection

Public Property Get CustomerID as Long
    CustomerID = m_id
End Property

Public Property Get Orders as VBA.Collection
    If m_orders Is Nothing then GetOrderCollection
    Set Orders = m_orders
End Property

Private Sub GetOrderCollection
    Const SQL As String = _
        "SELECT OrderID FROM tOrder " & _
        "WHERE CustomerID = "

    Dim cod as cOrder

    Set m_orders = New VBA.Collection
    With CurrentDb.OpenRecordset(SQL & m_id)
        Do While Not .EOF
            Set cod = New cOrder
            cod.Load !OrderID, Me  ' cCustomer passes a reference to itself, to the child
            m_orders.Add cod        ' and the order is added to the cCustomer.Orders collection
            .MoveNext
        Loop
        .Close
    End With
End Sub

Public Sub PayOrder(OrderID as Long)
    Dim Handled as Boolean
    RaiseEvent PaidOrder(OrderID, Handled)
End Sub
So see how the cCustomer class automatically creates a collection of cOrder objects, and in the constructor call in the loop...
Code:
cod.Load !OrderID, Me
... a reference to the "parent" is passed to every "child." The child, as a result, is not only aware of the parent, it can even handle events the parent might raise. In this example, the cCustomer class exposes a PayOrder method, which raises an event that ALL orders in the cCustomer.Orders collection will handle. The order with the matching ID will recognize the event and handle it, marking itself as paid. This way, if the parent exposes a collection class, and children in that collection are passed a reference to the parent in the constuctor, then the parent can communicate with all members of that child collection via events (and obviously individual classes more simply). In addition, all child objects can call back to the parent, because they all contain a live reference to the parent instance.

This is how any container class, and any set of classes it creates, can have two-way communication, even between a single "parent", and a huge list of children inside a collection.

Cheers,
Mark
 

ironfelix717

Registered User.
Local time
Today, 00:37
Joined
Sep 20, 2019
Messages
193
Hi,

I am revisiting to clarify my solution, which uses a 'hook' method in each child class to reference the parent...


First lets look at an example of how we want the end result to look....

Code:
Sub Test()
dim cls as new ClassParent

debug.print(cls.Child1.myfunction)

debug.print(cls.Child2.myfunction)

End Sub

Inside the parent....

Code:
Property Get Child1() As Child1Class
    Set Child1 = New Child1Class
    Child1.SetParent Me
End Property

Inside each child....

Code:
Private vParent as ClassParent

Sub SetParent(obj As ClassParent)  'this is the HOOK'
    Set vParent = obj
End Sub
 

MajP

You've got your good things, and you've got mine.
Local time
Today, 00:37
Joined
May 21, 2018
Messages
8,554
That does not really make sense to me. It is convoluted. I would do something like this.

Unfortunately in VBA you cannot overload methods or have parameterized constructors so you have to fake it.
this is how I would do it.
Class Child
Code:
Private m_Name As String
Private m_Color As String
Private m_Shape As String
Private m_Parent As ParentClass
Public Sub Initialize(Name As String, color As String, shape As String, Optional Parent As ParentClass)
  Me.Name = Name
  Me.color = color
  Me.shape = shape
  If Not Parent Is Nothing Then Set Me.Parent = Parent
End Sub

Public Property Get Name() As String
    Name = m_Name
End Property

Public Property Let Name(ByVal sNewValue As String)
   m_Name = sNewValue
End Property

Public Property Get color() As String
    color = m_Color
End Property

Public Property Let color(ByVal sNewValue As String)
    m_Color = sNewValue
End Property

Public Property Get shape() As String
    shape = m_Shape
End Property

Public Property Let shape(ByVal sNewValue As String)
    m_Shape = sNewValue
End Property
Public Property Get Parent() As ParentClass
    Set Parent = m_Parent
End Property
Public Property Set Parent(ByVal objNewValue As ParentClass)
    Set m_Parent = objNewValue
End Property
Public Property Get ToString() As String
  ToString = "Child Class - Name: " & Me.Name & ", Color: " & Me.color & ", Shape: " & Me.shape & ", ParentName: " & Me.Parent.Name
End Property

Class ParentClass
Code:
Option Compare Database
Option Explicit

Private m_Child1 As Child
Private m_Name As String
Public Property Get Name() As String
    Name = m_Name
End Property

Public Property Let Name(ByVal sNewValue As String)
    m_Name = sNewValue
End Property

Public Property Get Child1() As Child
    Set Child1 = m_Child1
End Property

Public Property Set Child1(ByVal objNewValue As Child)
    Set m_Child1 = objNewValue
    Set m_Child1.Parent = Me
End Property

Public Sub AddChild1(Name As String, color As String, shape As String)
  Set Me.Child1 = New Child
  Me.Child1.Initialize Name, color, shape, Me
End Sub

This gives me 2 techniques to set the child. I can pass in the child properties in my addChild1 method through the parent. Or I can at first create the child and then set it as a property of the parent.
Code:
Public Sub Test()
  Dim Prt As New ParentClass
  Prt.Name = "Parent One"
  'Method 1 create child through parent method
  Prt.AddChild1 "Child One", "Blue", "Round"
  Debug.Print "Parent Name " & Prt.Name & vbCrLf & " " & Prt.Child1.ToString

  'Method 2 create child first add to parent
  Dim cd As New Child
  cd.Initialize "Child One New", "Red", "Square"
  Set Prt.Child1 = cd
  Debug.Print "Parent Name " & Prt.Name & vbCrLf & " " & Prt.Child1.ToString

End Sub

If I create the child without going through the parent add method, then I may or may not know the Parent. The parent may not be instantiated. So I make this optional in my artificial constructor (initialize)
 

MajP

You've got your good things, and you've got mine.
Local time
Today, 00:37
Joined
May 21, 2018
Messages
8,554
In the immediate window I would get as you expect
Code:
Parent Name Parent One
 Child Class - Name: Child One, Color: Blue, Shape: Round, ParentName: Parent One

Parent Name Parent One
 Child Class - Name: Child One New, Color: Red, Shape: Square, ParentName: Parent One
 

Users who are viewing this thread

Top Bottom