I've been thinking of taking a more object-orientated approach to my programs...I use classes a lot but have never actually made an entirely object-orientated program.
I have a question.
An example is, a chat program. There is a class for a User and a class for a Chat Room. Each user has a Chat Room property which is a ChatRoom object.
Now, if I were to send a message to all users in that chat room, how would I compare Chat Room objects? Because you can't do:
I'm still learning the ins and outs of Classes so this might be a bit off, but here's what I'm thinking I would do.
A class (say clsChatClient) could be used to manage an array (or collection) of users and an array of rooms.
Each clsUser would have a clsRoom object set to one of the rooms in the Room array.
Each clsRoom would have an array of clsUser objects set from the User array.
There would be only one instance of each room and of each user but several references between each other.
It seems the more "object-orientated" it gets the more overhead there is. I know some overhead is normal but it makes me wonder if I'm doing it wrong.
I'm not 100% clear on what you mean, though. I'm going to post the code I have so far for users and chat room classes (I changed them), and maybe you can give me an example of what you mean.
It might be easier to just copy/paste these into class modules in VB so it is easier to follow.
clsUser.cls
Code:
Option Explicit
Private p_Username As String
Private p_Password As String
Private p_Version As clsVersion
Private p_TCPBuffer As String
Private p_Banned As Boolean
Private p_BannedStart As Date
Private p_BannedEnd As Date
Public Socket As MSWinsockLib.Winsock
Public Property Get BannedEnd() As Date
p_BannedEnd = p_BannedEnd
End Property
Public Property Let BannedEnd(ByVal NewValue As Date)
p_BannedEnd = NewValue
End Property
Public Property Get BannedStart() As Date
BannedStart = p_BannedStart
End Property
Public Property Let BannedStart(ByVal NewValue As Date)
p_BannedStart = NewValue
End Property
Public Property Get Banned() As Boolean
Banned = p_Banned
End Property
Public Property Let Banned(ByVal NewValue As Boolean)
p_Banned = NewValue
End Property
Public Property Get Username() As String
Username = p_Username
End Property
Public Property Let Username(ByRef NewValue As String)
p_Username = NewValue
End Property
Public Property Get Password() As String
Password = p_Password
End Property
Public Property Let Password(ByRef NewValue As String)
p_Password = NewValue
End Property
Public Property Get Version() As clsVersion
Set Version = p_Version
End Property
Public Property Set Version(ByRef NewValue As clsVersion)
Set p_Version = NewValue
End Property
Public Property Get TCPBuffer() As String
TCPBuffer = p_TCPBuffer
End Property
Public Property Let TCPBuffer(ByRef NewValue As String)
p_TCPBuffer = NewValue
End Property
Private Sub Class_Terminate()
If Socket.State <> sckClosed Then Socket.Close
End Sub
clsRoom.cls
Code:
Option Explicit
Private p_Name As String
Private p_Password As String
Private p_MaxUsers As Integer
Private p_Public As Boolean
Private p_UserCreated As Boolean
Private p_Sticky As Boolean
Private p_Users() As clsUser
Public Property Get Name() As String
Name = p_Name
End Property
Public Property Let Name(ByRef NewValue As String)
p_Name = NewValue
End Property
Public Property Get Password() As String
Password = p_Password
End Property
Public Property Let Password(ByRef NewValue As String)
p_Password = NewValue
End Property
Public Property Get MaxUsers() As Integer
MaxUsers = p_MaxUsers
End Property
Public Property Let MaxUsers(ByVal NewValue As Integer)
If NewValue > -1 Then p_MaxUsers = NewValue
End Property
Public Property Get IsPublic() As Boolean
IsPublic = p_Public
End Property
Public Property Let IsPublic(ByVal NewValue As Boolean)
p_Public = NewValue
End Property
Public Property Get UserCreated() As Boolean
UserCreated = p_UserCreated
End Property
Public Property Let UserCreated(ByVal NewValue As Boolean)
p_UserCreated = NewValue
End Property
Public Property Get Sticky() As Boolean
Sticky = p_Sticky
End Property
Public Property Let Sticky(ByVal NewValue As Boolean)
p_Sticky = NewValue
End Property
Private Function Users_UBound() As Integer
On Error GoTo ErrorHandler
Users_UBound = CInt(UBound(p_Users))
Exit Function
ErrorHandler:
Users_UBound = -1
Exit Function
End Function
Public Property Get User(ByVal Index As Integer) As clsUser
Set User(Index) = p_Users(Index)
End Property
Public Property Set User(ByVal Index As Integer, ByRef NewValue As clsUser)
Set p_Users(Index) = NewValue
End Property
On the server, there will be an array of users and an array of rooms.
When you have a "short circuit" property getter/setter pair that add no value (no editing, reformatting, or other actions) you're just as well off to just declare a Public variable and let that be the property. The compiler will end up making the getter/setter pair for you. The reason to explicitly declare these routines is to leave one out (making a property read- or write-only) or to do something else along the way (fetching from somewhere like a Recordset's Field, etc. or some other manipulation).
A "Room" object could easily have a property that is a collection "Users." You could store a set of User ID values there, or a User object itself.
Your "User" object does need a few things. Personally for a chat server I almost always create a UserControl that contains the Winsock control which is used to talk to that user. Yes, you'll want to pass through several of the Winsock methods, events, and properties. But you can add other properties, for example the User name, as well as a handy InUse boolean property to track which User slot is free in your control array when you're scanning to find a new one to Accept with when a ConnectionRequest comes in. Remember: a control is just a special kind of class.
Then it becomes possible for your Room object's Users property to be a Collection as suggested. You can add the User's control array index to this Collection when the User enters the Room. At the same time you'll probably want a Room property in your User UserControl, and you can set this to the Room object or array index when the User enters the Room too. To do room broadcasts you can just For Each through the collection of User indexes.
So each User has a Room, and each Room has a Collection of Users.
Also, it is more standard practice to prefix a class' module-private data with "m" or "m_" instead of "p_" but as long as you're consistent about it go for it.
Last edited by dilettante; May 17th, 2008 at 11:19 AM.
Here's my sketchy effort for what I meant. It uses Collections rather than arrays and assumes that no duplicate Usernames or Roomnames are allowed. I removed nearly all the properties to keep it simple. If you anticipate a large number of Users then arrays might be better (but would involve a load more code.)
It is very sketchy, I've not used Collections very much (managed variant arrays, yuck) and I'm learning classes.
Now, if I were to send a message to all users in that chat room, how would I compare Chat Room objects? Because you can't do:
vb Code:
If User1.ChatRoom = User2.ChatRoom Then
'Send data...
End If
Any ideas?
As you said you can't compare objects that way, not in VB not in any other language I know. In C++ this is solved creating an Operator Overload, in this case the = Operator would be overloaded and then you can ask (Obj1 = PObj2) because this would call your code where you verify property by property if both Objects are equal or not. VB doesn't allow this, but this is not a problem since you can make the same thing exactly creating your own Function i.e. Equals(Obj2 as Object) as a Public Function in your Class. So you need to create a Public Function inside the Class that returns a Boolean, inside this Function just check if all properties match (If Something = Obj2.Something And ...) , if they do just make this Function return True.
I haven't taken a look at Milk's code yet, but something you might want to do is add some functions to the object holding the collection to allow searching through the collection if you don't know the key (since VB6 doesn't include them.) This can be done by looking for the value of a certain property.
Here's a line from one of my programs on what that would look like:
Code:
Dim strKey as key
strkey = Botnet.GetIndexID(Index)
- or -
StrKey = Botnet.GetIDByName(Playername)
What those functions do is loop through the collection to find a matching value. If not, then then are returned as null (since they are strings.) If the string is not null, then the string would be used as a collection key.