subclassing

B

Beth

Hello.

I'm trying to figure out how to create subclasses with properties specific
to the subclass and so far it isn't going well.

Right now I have a class with an enum representing the type. The class has
all the properties specific to all the types, but what I want instead is to
move those properties to subclasses specific to each type.

I have code like this:

Friend Class ValidateValue
Private _textMaxLen As Integer
Private _textMinLen As Integer
Private _validateType As validateTypes

Friend Sub New(ByVal validate As String)
Dim compoundType As String
Dim remainder As String
Dim openParenPos As Integer
Dim commaPos As Integer

openParenPos = InStr(validate, "(")
If openParenPos > 0 Then
compoundType = Mid(validate, 1, openParenPos - 1)
remainder = Mid(validate, openParenPos + 1)
remainder = Mid(remainder, 1, Len(remainder) - 1) ' strip closing paren
Select Case compoundType
Case "text", "textnum"
If compoundType = "text" Then
_validateType = validateTypes.text
Else
_validateType = validateTypes.textNum
End If
commaPos = InStr(remainder, ",")
If commaPos = 0 Then
_textMaxLen = CInt(remainder)
Else
_textMaxLen = CInt(Mid(remainder, 1, commaPos - 1))
_textMinLen = CInt(Mid(remainder, commaPos + 1))
End If

But since the maxLen and minLen properties are specific to the two text
types, what I want to do is declare a subclass which inherits from the
existing class and contains and sets the validateType, textMaxLen, and
textMinLen properties given the compoundType and remainder.

The problem I'm having is with the subclass's constructor. In the subclass,
I want to pass the compoundType and remainder, and have code like:
'Friend Class ValidateText
' Inherits ValidateValue

' Private _textMaxLen As Integer
' Private _textMinLen As Integer
' Private _validateType As validateTypes

' Friend Sub New(ByVal compoundType As String, ByVal remainder As String)
' Dim commaPos As Integer
' If compoundType = "text" Then
' _validateType = validateTypes.text
' Else
' _validateType = validateTypes.textNum
' End If
' commaPos = InStr(remainder, ",")
' If commaPos = 0 Then
' _textMaxLen = CInt(remainder)
' Else
' _textMaxLen = CInt(Mid(remainder, 1, commaPos - 1))
' _textMinLen = CInt(Mid(remainder, commaPos + 1))
' End If
' End Sub

But that doesn't work because the base class doesn't have a constructor
without parameters. I want to use a parameter into a base class as a key to
determine the subclass to create and then create one of my subclasses.

I'm thinking in the base class I need a variable like:
Private _child as ValidateValue

And in the base class's constructor, have code like:
Select Case compoundType
Case "text", "textnum"
_child = new ValidateText(compoundType, remainder)

But I can't get the subclass to work.

What should I be doing differently?

Thanks for any help,

-Beth
 
F

Family Tree Mike

Well, is there a reason you don't want an empty constructor in
ValidateValue? You don't necessarily have to call it. After that, it seems
like you probably meant to have _textMaxLen, _textMinLen and _validateType
be protected, not private.
 
O

OmegaSquared

Hello, Beth,

You can call your base class's constructor (with an argument for the
validate parameter) as the first executable line of the inherited classes
constructor.

Also, as Mike says, you probably want the mentioned variables to be
Protected (not private) and then remove the corresponding declarations from
the inherited class.

Cheers,
Randy
 
B

Beth

Hi, ftMike.
Thanks for your response.

I renamed the constructor in both classes, but it's not working the way I
expected.

Now I have:
Friend Class ValidateValue
Private _child As ValidateValue
....
Friend Sub init(ByVal validate As String)
Dim compoundType As String
Dim remainder As String
Dim openParenPos As Integer
Dim commaPos As Integer

openParenPos = InStr(validate, "(")
If openParenPos > 0 Then
compoundType = Mid(validate, 1, openParenPos - 1)
remainder = Mid(validate, openParenPos + 1)
remainder = Mid(remainder, 1, Len(remainder) - 1) ' strip closing paren
Select Case compoundType
Case "text", "textnum"
_child = New ValidateValueText
_child.inittext(compoundType, remainder) ' this doesn't compile
....
Friend Class ValidateValueText
Inherits ValidateValue

Private _textMaxLen As Integer
Private _textMinLen As Integer
Private _validateType As validateTypes

Friend Sub initText(ByVal compoundType As String, ByVal remainder As String)
....

but the _child object in ValidateValue acts like a ValidateValue object in
intellisense (the way it was declared,) not a ValidateValueText object (the
way it was instantiated.)

So I have an 'is-a' relationship, like 'a dog is a mammal,' and I'm trying
to figure out how to pass a mammalID to the Mammal class, which, if it's
determined to be a dog, then creates an instance of a Dog object made
available to the caller, along with setting the mammalType property to 'dog'.

What I have is roughly equivalent to:
Friend Class Mammal
Private _child As Mammal
....
Friend Sub init(ByVal mammalID As String)
Dim compoundType As String
Dim remainder As String
Dim openParenPos As Integer
Dim commaPos As Integer

openParenPos = InStr(mammalID, "(")
If openParenPos > 0 Then
compoundType = Mid(mammalID, 1, openParenPos - 1)
remainder = Mid(mammalID, openParenPos + 1)
remainder = Mid(remainder, 1, Len(remainder) - 1) ' strip closing paren
Select Case compoundType
Case "dog", "wolf"
_child = New MammalDog
_child.initDog(compoundType, remainder)
....
Friend Class MammalDog
Inherits Mammal

Private _isWild as Boolean
Private _mammalType As mammalTypes

Friend Sub initDog(ByVal compoundType As String, ByVal remainder As String)
---
_child.initDog(compoundType, remainder) won't compile, as it doesn't see
initDog as a method of the _child object. It sees all the properties and
methods of Mammal.

What I'm trying to avoid is having something like:
Friend Class Mammal
Private _childDog As MammalDog
Private _childCat As MammalCat

but maybe that's what I need to do- have a member variable for each
subclass, like with composition (has-a).

I get the idea behind inheritance, but not how to actually implement it.

Thanks again for the help,

-Beth
 
B

Beth

Hi, OmegaSquared.
Thanks for your response.

My new properties only apply to the subclass, not the superclass or any
other subclass.

The superclass is passed a parameter it uses to determine which subclass to
create, so I don't want the caller instantiating the inherited class. I want
the caller instantiating the superclass with a subclassID and then getting
the instance of the subclass from a property of the superclass (_child as
superclass.)

But I can't seem to get that to work, so for now I have all the properties
specific to all the subclasses in the superclass.

Thanks again for your help,

-Beth
 
T

Tom Shelton

Hello, Beth,

You can call your base class's constructor (with an argument for the
validate parameter) as the first executable line of the inherited classes
constructor.

Also, as Mike says, you probably want the mentioned variables to be
Protected (not private) and then remove the corresponding declarations from
the inherited class.

Cheers,
Randy

Minor nitpick - but, I have to disagree with the making the fields protected.
I suggest one NEVER directly expose fields outside the class they are defined
in.

If you want subclasses to have access to a field in the base class, they you
should provide that access as a Protected property rather then a protected
field.

If you think about it, the same reasons you never make fields public apply to
making a field protected.
 
F

Family Tree Mike

You are declaring _child as ValidateValue, but instantiating it as a
ValidateValueText. This can be fine, but, it really is a ValidateValue
because of the declaration. This is why you get an error calling initText.

In general, you should not try and do so much in the baseclass
(ValidateValue). Things specific to your sub class should be handled in the
subclass (ValidateValueText).

For example, back to your Mammal, Dog, Wolf, I have added Rhinocerous. I
only place Wild as an attribute to the mammal class. Horns applies to
relatively few mammals, and does not belong necessarily as part of the Mammal
class. If there were sufficient horned animals you may want a HornedMammal
class to derive from mammal. Hopefully this helps.


Friend Class Mammal
Protected Wild As Boolean
End Class

Friend Class Dog
Inherits Mammal
Public Sub New()
Wild = False
End Sub
End Class

Friend Class Wolf
Inherits Mammal
Public Sub New()
Wild = True
End Sub
End Class

Friend Class Rhinocerous
Inherits Mammal
Protected Horns As Integer
Public Sub New()
Wild = True
Horns = 1
End Sub
End Class
 
O

OmegaSquared

Hi, Beth,

OK, I think I understand now about your "new properties". So you should
just remove these fields entirely from the base class. Also, Tom's point
about making the fields private and exposing them via properties is more
valid than just a "minor nitpick". (The only thing I might soften is the use
of the word "NEVER". I rarely use such absolutes.)

But I'm afraid that I'm not following what it is that you want to do. When
you instantiate an object from the inherited class, the fields, properties
and methods of the base class are also instantiated. If you instantiate an
object from the base class, it will not have the attributes of a derived
class.

I suppose you could create some sort of sub-class "factory" within the base
class, along the lines of the following. But it's not clear to me that this
is what you are looking for.

Cheers,
Randy

Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click

Dim SubObject1 As MyFirstInheritedClass
SubObject1 =
DirectCast(MyBaseClass.SubClassCreator(MyBaseClass.validateTypes.text),
MyFirstInheritedClass)
MsgBox(SubObject1.WhatSortAmI)

Dim SubObject2 As MySecondInheritedClass
SubObject2 =
DirectCast(MyBaseClass.SubClassCreator(MyBaseClass.validateTypes.textnum),
MySecondInheritedClass)
MsgBox(SubObject2.WhatSortAmI)

End Sub
End Class

Public Class MyFirstInheritedClass
Inherits MyBaseClass

Public Sub New()
MyBase.New("I'm a ""text"" sort of object")
End Sub

End Class

Public Class MySecondInheritedClass
Inherits MyBaseClass

Public Sub New()
MyBase.New("I'm a ""textnum"" sort of object")
End Sub

End Class

Public Class MyBaseClass

Friend Enum validateTypes
text
textnum
End Enum

Private _validate As String

Public Sub New(ByVal validate As String)
_validate = validate
End Sub

Friend Shared Function SubClassCreator(ByVal SubClassType As
validateTypes) As MyBaseClass
Dim Result As MyBaseClass = Nothing
Select Case SubClassType
Case validateTypes.text
Result = New MyFirstInheritedClass
Case validateTypes.textnum
Result = New MySecondInheritedClass
End Select
Return Result
End Function

Friend ReadOnly Property WhatSortAmI() As String
Get
Return _validate
End Get
End Property
End Class
 
B

Beth

Hi again, ftMike.

OK, let's say I have your classes below and a mammalID which is either 'D',
'W', or 'R'.
I want to pass the mammalID somewhere (maybe not to the base Mammal class,
but to some Mammal factory) and based on its value, create a more specific
mammal, such as a Dog.

The caller knows the mammalID and knows the result will be some kind of
mammal, just not which one. I was thinking I should pass the mammalID to the
Mammal's constructor, but maybe that factory function should be outsourced to
another class.

If it is outsourced to a MammalFactory, what would that class look like?

Here's what I don't want:
Friend Class MammalFactory
Private _dog as Dog
Private _wolf as Wolf
Private _rhino as Rhinocerous
Private _mammalType as Mammals

Enum Mammals
dog
wolf
rhino
End Enum

Friend Sub New (mammalID as string)
Select case mammalID
case 'D'
_dog = new Dog
_mammalType = dog
case 'W'
_wolf = new Wolf
_mammalType = wolf
case 'R'
_rhino = new Rhinocerous
_mammalType = rhino
End Select
End Sub

friend readonly property mammalType() as mammals
return _mammalType

Friend readonly property theDog() as Dog
return _dog

In other words, I'm trying to avoid having a property for each subclass, but
maybe that's the only way it will work.

The caller would have code like:
private sub makeMammal()
dim mammalID as string
dim theMammalFactory as MammalFactory

mammalID = InputBox("What kind of mammal do you want? Enter D, W, or R.")
theMammalFactory = new MammalFactory(mammalID)
If theMammalFactory.isWild then
Msgbox("Watch out! I made a wild animal.")
End if
select case theMammalFactory.mammalType
case dog
dim myDog as Dog
myDog = theMammalFactory.theDog
Msgbox("I made a dog! It can bark! Listen... " & myDog.bark)

I thought subclassing bought you some sort of convergence, but maybe that's
not at the implementation level, just at an interface level.

Thanks again for your response,

-Beth
 
F

Family Tree Mike

By the way, I agree with the other guys about the properties vs. attributes,
but will stick with what we have here.

I think to follow with your MammalFactory, I would write it like this:

Class MammalFactory
Shared Function CreateMammal(ByVal mammalID As String) As Mammal
Select Case mammalID
Case "D"
Return New Dog
Case "W"
Return New Wolf
Case "R"
Return New Rhinocerous
End Select
Return Nothing
End Function
End Class


Then your caller might do:

Sub Main()
Dim fido As Mammal = MammalFactory.CreateMammal("W")

If (TypeOf fido Is Dog) Then
Console.Out.WriteLine("Doggie!")
Else
Console.Out.WriteLine("No doggie!")
End If

If (fido.Wild) Then
Console.Out.WriteLine("Wild")
Else
Console.Out.WriteLine("Crazy")
End If

Console.In.ReadLine()
End Sub
 
B

Beth

Hi, again, Omega.

I'll try explaining what I'm trying to do another way.

Let's say at runtime, the user wants to edit a value and I've looked in the
database and determined how to validate the value they want to edit. A value
might be displayed and validated as a text field limited to numeric entry
with a minimum length of 1 and a maximum length of 15 characters, for
example. In this case, the validation string is 'textnum(1,15)'

I'm doing the string parsing in my existing ValidateValue class, except it
includes properties like textMinLen and textMaxLen which I'd prefer to move
(as you suggest) to a ValidateValueText class, because those properties only
apply to text values, not combo box or memo values.

So given a validation string, I can determine which subclass I need to
create, and would like to create it passing the additional details it needs
to set its own properties.

Then I need the caller to get a reference to the newly instantiated subclass
through a property of its instance of the base class.

And I'm not sure how to do that without creating properties for each of my
subclasses.

It's similar to your Function SubClassCreator, which returns a value of the
base class type, not of a specific subclass, except that the caller doesn't
know which subclass to create. The base class does after it parses the
validation string.

I thought I could declare a variable of the base class type and assign it to
a new instance of a subclass and get to the subclass's methods, but it
doesn't work that way.

Sorry if this is confusing, but I need to create an instance of the base
class first, which then can determine which subclass to create and then
create it so it can be referenced by the caller.

Or maybe this is just a poor use of subclassing and I should keep it the way
I have it, I don't know.

Thanks again for your help,

-Beth
 
B

Beth

I think I got it.

I need a module-level variable declared as the base class type exposed
through a property, like this:
Friend Class ValidateValue
Private _child As ValidateValue

Friend ReadOnly Property child() As ValidateValue
Return _child

but once I know which subclass to create, I need to declare another variable
of the subclass type, set its properties, and then set that subclass variable
to my module-level base class variable, like:

Select Case compoundType
Case "text", "textnum"
Dim childText As ValidateValueText
childText = New ValidateValueText
childText.initText(compoundType, remainder) ' sets validateValue to
text or textnum
_child = childText

Then the caller can access the base class's child property and get an object
of whichever subclass is appropriate, and convert it explicitly to the
correct subclass based on the validateType set in the subclass.

So I think I need to change
Private _validateType As validateTypes
in the base class to protected, or something.

I can work with that some more- at least the thing compiles now!

Thanks again for the help,

-Beth
 
O

OmegaSquared

Hello, Beth,

Is the following possibly the sort of thing that you are trying to do?

Public Class Form1

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click

Dim validate As String = "textnum(1,15)" ' validate would come
from DB in real application.
Dim NewObject1 As MyBaseClass = MyBaseClass.SubClassCreator(validate)
Dim EntryOK As Boolean = NewObject1.ValidEntry("some text entry")

validate = "text(1,15)" ' validate would come from DB in real
application.
Dim NewObject2 As MyBaseClass = MyBaseClass.SubClassCreator(validate)
EntryOK = NewObject2.ValidEntry("some text entry")

End Sub
End Class

Public Class MyBaseClass

Private _validate As String

Public Sub New(ByVal validate As String)

_validate = validate

End Sub

Friend Shared Function SubClassCreator(ByVal validate As String) As
MyBaseClass

Dim validateType As String = validate.Split("("c)(0)
Dim Result As MyBaseClass = Nothing
Select Case validateType
Case "text"
Result = New MyFirstInheritedClass
Case "textnum"
Result = New MySecondInheritedClass
End Select
Return Result

End Function

Friend Overridable ReadOnly Property ValidEntry(ByVal Entry As String)
As Boolean
Get
MsgBox(Entry & " is being validated by MyBaseClass.")
End Get
End Property
End Class

Public Class MyFirstInheritedClass

Inherits MyBaseClass

Public Sub New()
MyBase.New("I'm a ""text"" sort of object")
End Sub

Friend Overrides ReadOnly Property ValidEntry(ByVal Entry As String) As
Boolean
Get
MsgBox(Entry & " is being validated by the
MyFirstInheritedClass.")
End Get
End Property

End Class

Public Class MySecondInheritedClass

Inherits MyBaseClass

Public Sub New()
MyBase.New("I'm a ""textnum"" sort of object")
End Sub

Friend Overrides ReadOnly Property ValidEntry(ByVal Entry As String) As
Boolean
Get
MsgBox(Entry & " is being validated by the
MySecondInheritedClass.")
End Get
End Property

End Class
 
B

Beth

Hi again, Omega.

The problem is the form doesn't know which subclass to create, it just has a
key that's used to look up the validate string in the db.

So I need to use the key to look up the validate string and pass it to the
subClassCreator, declared as the superclass, and then based on the
superclass.validateType property (either enum text, textnum, etc.) create an
instance of the text/grid/cbo subclass, if it's appropriate (memo types don't
have a subclass.)

The thing I was missing was I needed a module-level variable declared as the
superclass, then after parsing the string to determine the type (I'll look
into the .split method, thanks,) declare and create a new instance of the
subclass, then assign that new subclass instance to the module-level
superclass variable, exposed as a property to the caller (an edit form).

The form actually does the validation, but the subclass provides the details
of how the caller should do the validation.

Here's some of the code:
Public Class frmEdit
Private _validate As ValidateValue
Private _validateChild As ValidateValue
Private _validateType As ValidateValue.validateTypes

Friend Sub init(<params>)
<create displaySet>
_displaySetCol = displaySet.displaySetCol(ordinalPos) ' ordinalPos is
an input param
_isNothing = (_displaySetCol.validation Is Nothing)
If _isNothing Then
Exit Sub ' not everything can be edited
Else
_validate = _displaySetCol.validation
End If
_validateType = _validate.validateType
txtValue.Visible = (_validateType = ValidateValue.validateTypes.text) Or
(_validateType = ValidateValue.validateTypes.dateTime) Or
(_validateType = ValidateValue.validateTypes.textNum)
txtValueMultiline.Visible = (_validateType =
ValidateValue.validateTypes.memo)
cboValue.Visible = (_validateType = ValidateValue.validateTypes.cbo)
dgvValue.Visible = (_validateType = ValidateValue.validateTypes.grid)

validationMsg = ""
_validateChild = _validate.child
Select Case _validateType
Case ValidateValue.validateTypes.text,
ValidateValue.validateTypes.textNum
Dim valText As ValidateValueText
valText = _validateChild
txtValue.Text = value ' input param
txtValue.MaxLength = valText.textMaxLen
If _validateType = ValidateValue.validateTypes.textNum Then
validationMsg = "Enter a number."
ElseIf valText.textMinLen > 0 Then
validationMsg = "Enter at least " & valText.textMinLen & "
character(s)."
Else
validationMsg = ""
End If

Friend Class ValidateValue
' functions specific to validate column of one row of displaySetColumn table
Private _child As ValidateValue
Private _validateType As validateTypes

Friend Enum validateTypes
text
textNum
dateTime
memo
cbo
grid
End Enum

Friend Sub init(ByVal validate As String)
Dim compoundType As String
Dim remainder As String
Dim openParenPos As Integer

openParenPos = InStr(validate, "(")
If openParenPos > 0 Then
compoundType = Mid(validate, 1, openParenPos - 1)
remainder = Mid(validate, openParenPos + 1)
remainder = Mid(remainder, 1, Len(remainder) - 1) ' strip closing
paren
Select Case compoundType
Case "text", "textnum"
Dim childText As ValidateValueText
childText = New ValidateValueText
childText.initText(compoundType, remainder)
_validateType = childText.validateType
_child = childText ' this is the piece I was missing. I
thought childText should be declared as the supertype


Friend ReadOnly Property child() As ValidateValue
Get
Return _child
End Get
End Property

Friend ReadOnly Property validateType() As validateTypes
Get
Return _validateType
End Get
End Property

Friend Class ValidateValueText
Inherits ValidateValue

Private _textMaxLen As Integer
Private _textMinLen As Integer
Private _validateType As validateTypes

Friend Sub initText(ByVal compoundType As String, ByVal remainder As String)
Dim commaPos As Integer
If compoundType = "text" Then
_validateType = validateTypes.text
Else
_validateType = validateTypes.textNum
End If
commaPos = InStr(remainder, ",")
If commaPos = 0 Then
_textMaxLen = CInt(remainder)
Else
_textMaxLen = CInt(Mid(remainder, 1, commaPos - 1))
_textMinLen = CInt(Mid(remainder, commaPos + 1))
End If
End Sub

Friend Overloads ReadOnly Property validateType() As validateTypes
Get
Return _validateType
End Get
End Property

Friend ReadOnly Property textMaxLen() As Integer
Get
Return _textMaxLen
End Get
End Property

Friend ReadOnly Property textMinLen() As Integer
Get
Return _textMinLen
End Get
End Property

I didn't really understand how to implement subclassing, I just knew that's
what I 'should' be doing.

Thanks again for your responses. Trying to crawl around in someone else's
head can be a pain.

-Beth
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top