Valhalla Legends Forums Archive | .NET Platform | Re: Object Oriented Design Help

AuthorMessageTime
NicoQwertyu
How do you go about designing a project, with the OO approach in mind? I find that some projects seem easy to design, while others just don't seem to be possible. For instance, if I wanted to design a texas hold'em card game, that would be fairly simple. A card game is made up of a deck of cards, players, and some chips. The community cards, as well as the cards the players are holding, could all be considered carddeck objects.

Cardgame.CardDeck
Cardgame.CardDeck.Card forgot about that one. :)
[s]Cardgame.ChipStack[/s] not even necessary
Cardgame.Player

What about projects that don't seem to be made up of "objects?" I'm having a hard time designing anything that I can't find in the real-world.

Say I want to create a battle.net bot. What objects make up a bot? Before I got into .NET, I would think "what does a ________ need to do." Well, a bot needs to have a database, settings, a means of connecting, and some commands. However, the only one of those that seems like an "object" to me is a database. So how would you go about designing what and where those features go (to fit an OO model)?

Bot.Database
Bot.Database.Entry
Bot.Socket
Bot.________?
Bot.________._______?
Bot.________?
March 22, 2008, 4:14 PM
Myndfyr
Part of the interview of my first programming job after college was:

"Design a program that shuffles and sorts a deck of cards, then write it out to XML."

My approach to this was simple.

First, I need to have a Card object.  A Card object has two attributes that I'm interested in; specifically, a Rank (2 through Ace) and a Suit (Heart, Diamond, etc).  So I built up a Card class and a couple enumerations:
[code]
enum Suit { Club, Diamond, Heart, Spade }
enum Rank { Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King, Ace }
class Card {
  private Suit _suit;
  private Rank _rank;
  public Card(Suit suit, Rank rank) {
    if (!Enum.IsDefined(typeof(Suit), suit)) throw new ArgumentOutOfRangeException("suit");
    if (!Enum.IsDefined(typeof(Rank), rank)) throw new ArgumentOutOfRangeException("rank");
 
    _suit = suit;
    _rank = rank;
  }
  public Suit Suit { get { return _suit; } }
  public Rank Rank { get { return _rank; } }
}
[/code]

My next step was to define a deck of cards.  I knew that some of the behaviors they wanted was shuffling and sorting -- these behaviors become methods or subroutines.  So I set it up:
[code]
class Deck
{
    private Dictionary<Suit, Dictionary<Rank, Card>> m_deck;

    public Deck()
    {
        m_deck = new Dictionary<Suit, Dictionary<Rank, Card>>();

        for (int suit = 1; suit <= 4; suit++)
        {
            Dictionary<Rank, Card> currentSuitList = new Dictionary<Rank, Card>();
            for (int rank = 1; rank <= 13; rank++)
            {
                currentSuitList.Add((Rank)rank, new Card((Suit)suit, (Rank)rank));
            }
            m_deck.Add((Suit)suit, currentSuitList);
        }
    }

    public void Shuffle()
    {
        // what to do here?
    }

    public void Sort()
    {
        // what to do here?
    }
}
[/code]

Hopefully I haven't answered too much of your question yet.  The good news is that you're asking the correct questions

As to your Battle.net question, I've put a lot of work and thought to questions like this over the years (not just to Battle.net stuff).  What you're asking is the first step of a process we call decomposition

I'm working on a library right now that I'm calling BN#.  In some ways it is analogous to CSB, though I think it exhibits a little more thought than CSB did.  The important thing I'm trying to remember while I'm working on it, though, is that there are domain-specific and non-domain-specific objects that are going to end up in my library.  For example:

[list]
[*] I'm creating a class called BattleNetClient.  That's the central class used to connect to Battle.net, and clearly it is specific to the problem domain.  But, this class derives from another class called ConnectionBase (not dissimiler to the one that I linked).  Because a public class inherits from this class, this class must also be public -- that's part of the language rules.  The important thing to note is that I'm willing to go along with that rule.
[*] A channel might have a list of User objects present.  This is domain-specific because it deals with Battle.net's services.
[*] It might also have settings -- this class is probably not domain-specific because it deals more with the way that your bot handles things and not so much Battle.net itself.
[/list]

It looks like you're on your way!  Hopefully this all helps.
March 22, 2008, 6:22 PM
NicoQwertyu
Whoops, I forgot about each card in the deck: editted.  ;)

[quote]I'm creating a class called BattleNetClient.  That's the central class used to connect to Battle.net, and clearly it is specific to the problem domain.  But, this class derives from another class called ConnectionBase (not dissimiler to the one that I linked).  Because a public class inherits from this class, this class must also be public -- that's part of the language rules.  The important thing to note is that I'm willing to go along with that rule.[/quote]

I went ahead and read through the ConnectionBase class, and I was very pleased that I could read through it and understand what's going on, even if I couldn't have written the class myself.
[code]byte[] incBuffer = new byte[len];

...

totRecv += localNS.Read(incBuffer, totRecv, (int)len - totRecv);

...

return incBuffer;[/code]
Is an implementation like this thread safe? And is it possible to have events like the old WinSock control, or is this a bad idea?

[quote]A channel might have a list of User objects present.  This is domain-specific because it deals with Battle.net's services.[/quote]
These are the kind of things I have no trouble applying the object oriented model to. This is very similar to the deck of cards example.

[quote]It might also have settings -- this class is probably not domain-specific because it deals more with the way that your bot handles things and not so much Battle.net itself.[/quote]
This is my problem: abstract kinds of things. A setting isn't a physical object like a user, registry key, or database entry is. Would settings be its own class (Bot.Settings)? Or would I want to break it down more? Would a single setting be its own object?

[quote]But, this class derives from another class called ConnectionBase (not dissimiler to the one that I linked).  Because a public class inherits from this class[/quote]
Now for the kicker. After I read through ConnectionBase, I loved the idea of it. I wouldn't have thought to make a very basic connection class, then have my more specific classes derive from it. For instance, an IRCClient that inherits (I think I'm using the term right) ConnectionBase. And a BattleNetClient that inherits ConnectionBase. Or an AolClient etc...

So I want to steal that idea...

I mentioned earlier about "Bot.Database," and "Bot.Database.Entry." Keep in mind I'm coming up with this as I go, so there is bound to be logical flaws and problems. Here's how I originally thought about it:

[code]Namespace Bot

Public Class Database

Public Property DatabaseName as String

Public Function Add(Bot.Database.Entry) as Integer 'return an error or 0
Public Function Remove(Bot.Database.Entry)  as Integer 'return an error or 0
Public Function Enumerate(Bot.Database) as String()[/code]
Any other basic database properties and functions would go in here. Maybe some of these functions could be overloaded and made shared, so an instance of this object isn't needed. I have no idea.

[code]Namespace Bot
Namespace Database

Public class Entry

Public Property Username as String
Public Property Password as String[/code]
I'm not sure what kind of methods I could put in "entry," so I'm not sure if it's even necessary.

[code]'Usage
Dim myDatabase as Bot.Database = new Bot.Database
Dim myndFyre as Bot.Database.Entry = new Bot.Database.Entry

myndFyre.Username = "MyndFyre[vL]"
myndFyre.Password = "LOLPASS"
myDatabase.Add(myndFyre)[/code]
The problem I run into is reusability. If I want to use a database for anything other than username and password, my Entry class is unusable. So how can I fix this? Can I have a class derive from Database.Entry that has the specific entry format, or is that a waste of time? What's a good solution for this problem?



March 22, 2008, 7:17 PM
Myndfyr
[quote author=NicoQwertyu link=topic=17402.msg177191#msg177191 date=1206213465]
[code]byte[] incBuffer = new byte[len];

...

totRecv += localNS.Read(incBuffer, totRecv, (int)len - totRecv);

...

return incBuffer;[/code]
Is an implementation like this thread safe? And is it possible to have events like the old WinSock control, or is this a bad idea?
[/quote]
Yes, this implementation is thread-safe (depending on what you mean by it).  In the traditional sense of "thread-safe," you're not going to run into problems with the incBuffer having collisions, because incBuffer is declared locally to the method.  Since each thread gets its own local variable and call stack, you don't need to worry about incBuffer on Thread A colliding with incBuffer on Thread B - they're in different memory.

On that note, though, ConnectionBase is entirely its own thing; there shouldn't be multiple threads Read()ing from it at the same time.

The way that I typically use this class is that, in my derived class (BattleNetClient), it creates a new thread and listens on it.  Here's an example of the Listen() method:

[code]
        private void Listen()
        {
            while (IsConnected)
            {
                try
                {
                    byte[] header = Receive(4);
                    if (header[0] != 0xff)
                    {
                        DataFormatter.WriteToTrace(header);
                        throw new ProtocolViolationException(BattleNet.ProtocolViolation);
                    }
                    BncsPacketId id = (BncsPacketId)header[1];
                    ushort len = BitConverter.ToUInt16(header, 2);

                    byte[] remainder = Receive(len - 4);
                    DataReader dr = new DataReader(remainder);
                    switch (id)
                    {
                        #region SID_ENTERCHAT 0x0a
                        case BncsPacketId.EnterChat:
                            HandleEnterChat(dr);
                            break;
                        #endregion
                        #region SID_GETCHANNELLIST 0x0b
                        case BncsPacketId.GetChannelList:
                            HandleGetChannelList(dr, remainder.Length);
                            break;
                        #endregion
                        #region SID_CHATEVENT 0x0f
                        case BncsPacketId.ChatEvent:
                            HandleChatEvent(dr);
                            break;
                        #endregion
                        default:

                            break;
                    }
                }
                catch (SocketException se)
                {
                    OnError(se.Message, se);
                    Close();
                }
                catch (ProtocolViolationException pve)
                {
                    OnError(pve.Message, pve);
                    Close();
                }
            }
        }
[/code]

The gotcha by using this method - running this on a background thread - is that when events are fired from BattleNetClient, they have to be bubbled back to the user interface thread via Invoke().  My library actually takes care of this the same way that System.Timers.Timer does -- it provides this property:
[code]
public ISynchronizeInvoke SynchronizingObject { get; set; }
[/code]

For more information, see the documentation on System.ComponentModel.ISynchronizeInvoke.

[quote author=NicoQwertyu link=topic=17402.msg177191#msg177191 date=1206213465]
This is my problem: abstract kinds of things. A setting isn't a physical object like a user, registry key, or database entry is. Would settings be its own class (Bot.Settings)? Or would I want to break it down more? Would a single setting be its own object?
[/quote]
Sometimes.  And unfortunately, this is really the kind of thing that only experience can teach.  Sometimes you'll find that for certain tasks it's simply easier to deal with rework because something just can't be done the first way, or it's not as usable as you'd like.

[quote author=NicoQwertyu link=topic=17402.msg177191#msg177191 date=1206213465]Now for the kicker. After I read through ConnectionBase, I loved the idea of it. I wouldn't have thought to make a very basic connection class, then have my more specific classes derive from it. For instance, an IRCClient that inherits (I think I'm using the term right) ConnectionBase. And a BattleNetClient that inherits ConnectionBase. Or an AolClient etc...
[/quote]
Yep, you're using the term precisely right. 
[quote author=NicoQwertyu link=topic=17402.msg177191#msg177191 date=1206213465]
So I want to steal that idea...
[/quote]
That's why I make these things open-source :)

[quote author=NicoQwertyu link=topic=17402.msg177191#msg177191 date=1206213465]
I mentioned earlier about "Bot.Database," and "Bot.Database.Entry." Keep in mind I'm coming up with this as I go, so there is bound to be logical flaws and problems. Here's how I originally thought about it:

...

The problem I run into is reusability. If I want to use a database for anything other than username and password, my Entry class is unusable. So how can I fix this? Can I have a class derive from Database.Entry that has the specific entry format, or is that a waste of time? What's a good solution for this problem?
[/quote]
One of the things we do with this is consider a layered approach.  When dealing with data, it is very common to refer to each database "record" as an entity when it corresponds to an actual object.  For instance, consider the following database schema:

Table User:
-- ID int PK
-- Username varchar(50)
-- PasswordHash binary(20)

Table Setting:
-- ID int PK
-- SettingName varchar(50)
-- SettingValue varchar(50)

Table UsersSettings
-- UserID int PK FK
-- SettingID int PK FK

In this case, both User and Setting should be entities, and should probably have their own modeled classes.  They have an m:n relationship, and the RDBMS provides information between the two types.  Very usually, we make a base class called Entity that each of these can derive from.  It allows us to sometimes give additional functionality to database entity objects.

Consider this schema, though:

Table User:
-- ID int PK
-- Username varchar(50)
-- PasswordHash binary(20)

Table UserSettings:
-- UserID int PK FK
-- SettingName varchar(50) PK
-- SettingValue varchar(50)

In this example, User is an Entity, but since UserSettings is related to User via m:1 (UserSettings:User), then UserSettings should be a property of a User object.

Hope that helps!
March 22, 2008, 11:09 PM
NicoQwertyu
[quote]Yes, this implementation is thread-safe (depending on what you mean by it).  In the traditional sense of "thread-safe," you're not going to run into problems with the incBuffer having collisions, because incBuffer is declared locally to the method.  Since each thread gets its own local variable and call stack, you don't need to worry about incBuffer on Thread A colliding with incBuffer on Thread B - they're in different memory.[/quote]
You assumed right. That's what I ment.

[quote]Very usually, we make a base class called Entity that each of these can derive from.  It allows us to sometimes give additional functionality to database entity objects.[/quote]
So you do generally have to recode the database stuff though? You don't just reuse a few classes and build from there?

March 23, 2008, 12:27 AM
Myndfyr
[quote author=NicoQwertyu link=topic=17402.msg177208#msg177208 date=1206232032]
So you do generally have to recode the database stuff though? You don't just reuse a few classes and build from there?[/quote]
We reuse a couple classes, but most of it isn't implementation, it's style/architecture definition.

[code]
    public abstract class Factory<T> where T : Entity
    {
        protected virtual List<T> GetAllItemsFromReader(IDataReader reader)
        {
            List<T> list = new List<T>();
            while (reader.Read())
            {
                list.Add(GetItemFromReader(reader));
            }

            return list;
        }

        protected abstract T GetItemFromReader(IDataReader reader);

        public abstract IDbConnection CreateConnection();

        public abstract IDbTransaction CreateTransaction(IDbConnection con);

    }

    public abstract class Entity
    {
        private bool m_isNew;

        protected Entity()
        {
            m_isNew = true;
        }

        public virtual bool IsNew
        {
            get { return m_isNew; }
            protected set
            {
                m_isNew = value;
            }
        }
    }
[/code]
March 23, 2008, 12:43 AM
NicoQwertyu
If you can find the time and patience, look through what I've written:

Tag.vb
[code]Namespace Database
    Public Class Tag
        Private _tag As String
        Public Property Tag() As String
            Get
                Return _tag
            End Get
            Set(ByVal value As String)
                If value IsNot Nothing Then _tag = value
            End Set
        End Property
    End Class
End Namespace[/code]

User.vb
[code]Namespace Database
    Public Class User
        Protected _username As String
        Protected _password As String
        Protected _access As Integer

#Region "Properties"
        Public Property Username() As String
            Get
                Return _username
            End Get
            Set(ByVal value As String)
                If value IsNot Nothing Then _username = value
            End Set
        End Property

        Public Property Password() As String
            Get
                Return _password
            End Get
            Set(ByVal value As String)
                If value IsNot Nothing Then _password = value
            End Set
        End Property

        Public Property Access() As Integer
            Get
                Return _access
            End Get
            Set(ByVal value As Integer)
                _access = value
            End Set
        End Property
#End Region

    End Class
End Namespace[/code]

DatabaseCore.vb
[code]Imports Bot.Database
Imports System
Imports System.Collections.Generic

Public Class DatabaseCore : Implements IDisposable

    Protected _userList As List(Of User)
    Protected _safeList As List(Of Tag)
    Protected _shitList As List(Of Tag)

    Public Sub New()
        _userList = New List(Of User)
        _safeList = New List(Of Tag)
        _shitList = New List(Of Tag)
    End Sub

    Public Sub AddUser(ByVal newUsername As String, ByVal newPassword As String, ByVal newAccess As Integer)
        If newUsername IsNot Nothing And newPassword IsNot Nothing Then
            Dim u As User = New User
            With u
                .Username = newUsername
                .Password = newPassword
                .Access = newAccess
            End With
            _userList.Add(u)
        End If
    End Sub

    Public Sub AddSafelist(ByVal newTag As String)
        If newTag IsNot Nothing Then
            Dim t As Tag = New Tag
            t.Tag = newTag
            _safeList.Add(t)
        End If
    End Sub

    Public Sub AddShitlist(ByVal newTag As String)
        If newTag IsNot Nothing Then
            Dim t As Tag = New Tag
            t.Tag = newTag
            _shitList.Add(t)
        End If
    End Sub

    Public Function GetUser(ByVal index As Integer) As String
        Try
            Return _userList(index).Username
        Catch ex As ArgumentOutOfRangeException
            Return "Out of bounds exception! The index supplied does not correspond to a user!" 'Is there a better way to do this?
        End Try
    End Function

    Private disposedValue As Boolean = False        ' To detect redundant calls

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: free other state (managed objects).

                ' I don't know what a managed object is! :D
            End If

            ' TODO: free your own state (unmanaged objects).
            ' TODO: set large fields to null.

            ' I think these are unmanaged objects! :D
            _userList = Nothing
            _shitList = Nothing
            _safeList = Nothing
        End If
        Me.disposedValue = True
    End Sub

#Region " IDisposable Support "
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class[/code]
I don't know if implementing IDisposable is necessary, but I remember seeing you talk about it in a previous post, and I'm trying to get everything right.

Usage:
[code]Imports Bot
Imports Bot.Database
Public Class uiMainForm

    Private Sub uiMainForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim userDatabase As DatabaseCore = New DatabaseCore
        userDatabase.AddUser("Kyle", "abc123!@", 1)
        userDatabase.AddUser("Asylum", "abc123!@", 1)
        MessageBox.Show(userDatabase.GetUser(0))
        MessageBox.Show(userDatabase.GetUser(1))
        MessageBox.Show(userDatabase.GetUser(2))
    End Sub
End Class[/code]
Outputs:
Kyle
Asylum
Index out of bounds...


I want to appologize. I know you're trying to help me the best you can, and I'm just not seeming to get it. While this implementation will work, I'm essentially just having a class pass some glorified structs around. I originally started trying to inherit my superclass (DatabaseCore), but it ended up being pointless because of the way I designed it. What would I need to do to make this follow the object oriented model more appropriately?
March 23, 2008, 2:43 AM
FrOzeN
Firstly, I think what I'm saying is pretty accurate, but I may be wrong. Hopefully others will clarify any mistakes I make in this post.

A managed object is an object that's memory is handled by a garbage collector (in this case, .NET takes care of this for you, and automatically applies this to all the object's you instantiate). An unmanaged object is one that you have explicit control over it's memory, and it's up to you to free the memory when necessary.

The advantages of a managed object is that you don't have to worry about any of the million of things that could go wrong. The downfall is that you lose certain control and slight performance over that specific memory. The certain control you lose really only applies when doing relatively low-level things, or small tweaks and oddities that most programmers would never need to worry about. The slight loss of performance really only applies to those highly skilled programmers doing memory-intensive things as they'll be able to find optimizations. In most cases however, .NET is likely to handle the memory better than you could if you were doing it yourself, and the safety alone it provides you is brilliant.

Just a quick rundown of the safety about memory management so you have some idea of what I mean. When you use the 'new' keyword, it allocates a slot of memory (the size of whatever you allocated it for - i.e. Int32 would allocate 4 bytes[sup][1][/sup]) in the 'heap'[sup][2][/sup]. It then returns an address of where it's located (hence, by reference). The variable you assign this reference to is known as a pointer (it points to the address of where the memory is located). The dangers of this is that memory is now under your control. A pointer is simply a number (the address of the memory location). It tells you nothing about the size allocated. One of the major problems that occurs is that when parsing that pointer around, you may want to write to that section of the memory. If you write something longer than what was allocated (the size of the object/type you defined with 'new' for that pointer) is just writes over it. The memory outside the bound of your allocation could of been some very important data, another program may be relying on it, etc. This could to anything from a "lucky that's nothing important", to a slot that explorer.exe was using and a pretty blue screen. In case you're interested, this is where the terminology of words such as "buffer overflow, memory leaks, etc" come into play.

[sup][1][/sup] - Technically, it creates slightly more than 4 bytes as it also has to create memory to store the address. On that note, you can't, nor would need to, determine the actual size it allocates. For things like structures and objects, even if it seems they have an explicit size to be allocated, padding is added.

[sup][2][/sup] - Heap is an area of memory used for dynamic memory allocation. Not that this technical stuff matters to programming in VB.NET. I'm just clarifying the word.

The other issue with that memory is that once you've finished using it, you must free it (tell the computer you are no longer using it, so it can be allocated elsewhere). If your program crashes before you have the chance to free that memory, it will likely just sit there, wasted. In languages where memory is freed, if that memory was an instance of a class, the class' destructor (opposite of constructor - namely New() in VB.NET) is fired. This allows you to clean up a class, and possibly free any memory it may have allocated before it's removed. Now, this is where Dispose() comes in. In VB.NET, when an instance of a class goes out of scope, .NET flags it as unused, and eventually the garbage collector will free the memory. It doesn't instantly free it because there's no immediate need. Freeing memory does take processing time too, and .NET's memory management is designed in a way to free the memory at more appropriate times.

The idea of the IDisposable interface is to provide a standardized way of telling managed objects when they are no longer needed. This is important for objects that use unmanaged resources, or have things that need to be cleaned up immediately. The Using keyword comes into play to help you define the scope of an object's usage within your code. As you probably noticed from MyndFyre's post in this topic, the Dispose method is implicitly called at the end of a Using block. Hence a great example of the idea behind creating Interfaces for the use of standardization.

Now, in your code you set _userList, _shitList, and _safeList to 'Nothing'. Don't be fooled by that keyword. In Visual Basic 6 it would delete the object (free it's memory). But VB6 is an unmanaged language, unlike VB.NET which is managed. In VB6, when an object was deleted it would fire that object's destructor, which was the sub "Class_Terminate" within the .cls (class) file of that object. Or, for forms which where also objects in VB6, it would fire the sub "Form_Terminate" when you set the form to 'Nothing'. Note that if you set a form to 'Nothing' in VB6 before it was unloaded it wouldn't actually delete the object. Instead, it would just set the reference to the form to 'Nothing'. So any code you have interacting with the form would no longer work, yet, you could still access the form directly (like clicking on it). The form's destructor event was also only called when the form has been unloaded AND set to 'Nothing'. VB6 would automatically set form objects to 'Nothing' in most cases when all form's are unloaded, but sometimes it slipped up when the form was interacting with other objects and created all sorts of memory issues such as circular references, etc. Hence one of the reasons why you'd see so much code like this in VB6 programs:
[code]Unload frmAbout
Unload frmMain
Set frmAbout = Nothing
Set frmmain = Nothing[/code]
Anyway, getting back on track. In VB.NET, setting something to 'Nothing' just changes what the reference to the object (being the variable) is treated as. It doesn't do any explicit memory freeing, nor does it even call the Dispose() method. When the object goes out of scope, it will be handled like any other dynamically allocated object through .NET's garbage collection.

Back in your code again, for that scenario, I don't think you'd need to implement IDisposable. As I said before, the idea of it is to provide a way of telling an object that it needs to clean up it's unmanaged resources. For example, connecting to a SQL Database to read/write a bunch of data, or opening a file to read/write a bunch of data, etc. Holding onto that connection, or keeping the file handle open until the garbage collection gets around will waste resources. It will also prevent that file being accessed until it's handle is closed. By implementing IDisposable in those cases creates an insurance that once you've finished working with them, they'll be freed up properly.
[hr]

Going back to your original question: Determining what should be objects and how to properly design them.

Firstly, you gather all your ideas and such you want included in your bot class, and then start categorizing them. Categorizing works by defining precedence of needs. For example, the core bot object needs methods such as Connect and Disconnect, it also needs properties such as Username, Password, Client, and so on. Stuff like userlist, safelist, and shitlist are irrelevant at this point. So we put them aside for now. All we currently want to do is create a raw bot object. This object will be the overall [core bot] class, and further bot objects with more features will derive off it. This [core bot] object would be a derivative of the overall core [connection base] which you mentioned before.

To design the [core bot] object, it's easiest to start from the interface (not the Interface keyword). This interface initially consists of the public properties you would give it (this is assuming a project would directly interact with this object - as you develop them - the visibility of these public features may change into a protected).

Just off my head, I get some like this:

CoreBot (class)
[list]
[li]Methods: Connect(), Disconnect(), State(), SendChatMessage(), SetProfile(), etc.[/li]
[li]Shared Methods: NumberOfBotsConnected[/li]
[li]Properties: Username(), Password(), Profile()[sup][3][/sup], etc.[/li]
[li]Events: Connected(), UserJoinedChannel(), etc. - Note that it's recommended you follow the standard of parsing the 'sender' and 'e' (EventArgs - you'd customize this) to the function.[/li]
[/list]

[sup][3][/sup] - I would create a Profile structure with Age, Sex, Location, and Description properties. Age would currently be ReadOnly(), and Sex would have checking as it's currently read only for StarCraft and BroodWar clients.

Essentially, once everything is outlined. [Core bot] would do nothing more than manage a connection with Battle.net mimicking a game client, and have no non-Battle.net features. It would derive from [connection base], and I'd recommand creating a few classes such as BNCS_Packets, BNLS_Packets, etc. These classes would handle methods for handling and sending every BNCS and BNLS respectively. They would have a property which you supply a reference to the packet buffer which your [core bot] is using, so they can directly create and send or manipulate the packet and parse the information back to you.

Note that although being a core class (core bot), and unlikely to be used directly. It should still follow proper encapsulation. Try to picture the object's public properties and methods like a bunch of textboxes and buttons a screen. Any combination of clicking on the buttons, or weird obscure input in the textboxes should be handled by the program. You should never expect the user to know these things. The idea behind this encapsulation is also that if the user does in fact mistreat the object, it throws an exception. It should never directly send a message to the user via a message box or the like. Hence one of the main concepts behind the object-orientated philosophy.

A note with your code, I wouldn't use a Try/Catch in a situation where I could prevent it. For example, you have:
[code]Public Function GetUser(ByVal index As Integer) As String
    Try
        Return _userList(index).Username
    Catch ex As ArgumentOutOfRangeException
        Return "Out of bounds exception! The index supplied does not correspond to a user!" 'Is there a better way to do this?
    End Try
End Function[/code]

I'd change it to something like this:
[code]Public Function GetUser(ByVal index As Integer) As String
    If (index >= 0) And (index < _userList.LongCount) Then
        Return _userList(index).Username
    Else
        Throw New ArgumentOutOfRangeException("index", "Out of bounds exception! The index supplied does not correspond to a user!")
    End If
End Function[/code]

Secondly, regarding your database. There is no need to over complicate things just because you can. The Tag class seems quite redundant and using lists of strings would do just fine. Remember, these lists aren't exposed directly, so there's no need to be overly safe by doing things such as "If value IsNot Nothing Then". That doesn't really add anything. The methods that are used for adding more users to these lists is the place for safeguarding and error handling to go. Though, with a list of the User class, you should still provide some error checking within it. And use the 'Throw' keyword as I demonstrated above to expel the error into the calling procedure rather than parsing it through the return value. The calling procedure would use a Try/Catch to pickup any of the errors that your User class may throw.

Another approach which would probably work more cleanly would just have a single userlist. In that userlist, each user would have properties such as Safelisted, Shitlisted, etc. This way your bot only has to loop through one list to determine all properties. This also will improve the efficiency of the bot, especially when it has to run a check on a bunch of users (such as when it joins a new channel). You could further combine the userlist to contain all the necessary details about the user in the channel (such as ping, position in listview, etc).

Also, why does your User class have a Password property? Here's some things I'd have for a User:

User (class)
[list]
[li]Methods: LoadDatabase(filepath), SaveDatabase(filepath)[/li]
[li]Properties: Username (string), Access (integer), Flags (integer) - would use an enumeration of the different flag types, each enum would be a power of two so they could be combined, IsSafelisted (boolean), IsShitlisted (boolean), InChannel (boolean), ChannelInfo (structure ChanInfo) - would contain sub-properties if InChannel is true[/li]
[/list]

ChanInfo (structure)
[list]
[li]Properties: Ping, Index (position in list/listview), Client (eClients - an enumeration of game clients), flags (integer - Battle.net flags), etc.[/li]
[/list]

Now, although the property User.ChannelInfo.Index isn't required internally, it is handy to place this variable here as it allows the internal userlist to be tightly integrated with the users in the channel. The internal list shouldn't contact the external listview, but external code can access this value as fast method of grabbing user data from a channel list without the needs of further lookups. Small things like this can improve efficiency and help make things run smoother.

Ultimately the design of a project first comes down to what you want to do with it. You then think of things that may be done with the project. From that, you then look to see commonalities between code, or code that is rather generic. With this you can break it down into base classes, and use derivatives to define more specific features that the classes you want to use require. The more generic you can break a class down to, the more the reusability increases. Taking this into consideration, you shouldn't be excessively trying to break things down too far, such as your Tag class just to hold a single String.

Hopefully this post provides more information than it does exaggeration. I tried to explain things in fair detail so you can understand the reasoning behind them. :)
March 23, 2008, 11:59 AM
NicoQwertyu
First off, thank you for making that long post, and keeping it simple for me.

[quote]This object will be the overall [core bot] class, and further bot objects with more features will derive off it.[/quote]
Derive off it -- as in [core bot] will inherit the methods and properties of all other classes built around it? I doubt that's what you were trying to explain, but that's how I read it.  :-[

[quote]Anyway, getting back on track. In VB.NET, setting something to 'Nothing' just changes what the reference to the object (being the variable) is treated as. It doesn't do any explicit memory freeing, nor does it even call the Dispose() method. When the object goes out of scope, it will be handled like any other dynamically allocated object through .NET's garbage collection.

Back in your code again, for that scenario, I don't think you'd need to implement IDisposable. As I said before, the idea of it is to provide a way of telling an object that it needs to clean up it's unmanaged resources. For example, connecting to a SQL Database to read/write a bunch of data, or opening a file to read/write a bunch of data, etc. Holding onto that connection, or keeping the file handle open until the garbage collection gets around will waste resources. It will also prevent that file being accessed until it's handle is closed. By implementing IDisposable in those cases creates an insurance that once you've finished working with them, they'll be freed up properly.[/quote]
Is the massive List(of T) I have floating around in memory an unmanaged resource?

[quote]A note with your code, I wouldn't use a Try/Catch in a situation where I could prevent it. For example, you have:[/quote]
Ah, thank you. I love learning the correct way to code things. I knew I could throw my own exceptions, but it's something I've never done before. Now I know how.  :)

[quote] The Tag class seems quite redundant and using lists of strings would do just fine.[/quote]
You're right. I can't even come up with an excuse as to why I did that in the first place. Fixed.

[quote]Also, why does your User class have a Password property? Here's some things I'd have for a User:[/quote]I just used the first three properties that came to mind. I'm not writing a real bot, I'm just trying to learn the language by writing little mini-projects. Bots are something I've always been familiar and comfortable with, so I'm starting with that. I'd much rather learn .NET by making little pieces of a bot, rather than try to write the software for an ATM machine.

[quote]Now, although the property User.ChannelInfo.Index isn't required internally, it is handy to place ...[/quote]Speaking of the channel list, I had a little epiphany this morning while walking circles around the island in my kitchen, eating my egg/bacon sandwich. Although some of my code isn't reusable in the sense than I can just plug it in and go to town on any database, it can be reusable in my own project.

The channellist can inherit databasecore, and add a few methods for manipulating the control on the form, and changing icons. The user objects in the channel can derive from Database.User, and just add a few more properties for ping and icon. So it's not a total failure.

Edit: Well, the above is almost true. It's kind of impossible to implement without overriding just about everything, however.  >:(
March 23, 2008, 7:14 PM
FrOzeN
[quote author=NicoQwertyu link=topic=17402.msg177235#msg177235 date=1206299655][quote]This object will be the overall [core bot] class, and further bot objects with more features will derive off it.[/quote]
Derive off it -- as in [core bot] will inherit the methods and properties of all other classes built around it? I doubt that's what you were trying to explain, but that's how I read it.  :-[[/quote]"Derive off it" means they'll inherit it, not have it inherit them.

So [core bot] would be a raw structure for a bot. You then may want to add a bunch of moderation features as methods/properties. You could create an [ops bot] class and inherit the [core bot] (so you say "Ops bot class derives from the Core bot class"). In this [ops bot] class you now only have to implement the new features you wanted to add, such as a method like SweepBan(channel).

[quote author=NicoQwertyu link=topic=17402.msg177235#msg177235 date=1206299655][quote]Anyway, getting back on track. In VB.NET, setting something to 'Nothing' just changes what the reference to the object (being the variable) is treated as. It doesn't do any explicit memory freeing, nor does it even call the Dispose() method. When the object goes out of scope, it will be handled like any other dynamically allocated object through .NET's garbage collection.

Back in your code again, for that scenario, I don't think you'd need to implement IDisposable. As I said before, the idea of it is to provide a way of telling an object that it needs to clean up it's unmanaged resources. For example, connecting to a SQL Database to read/write a bunch of data, or opening a file to read/write a bunch of data, etc. Holding onto that connection, or keeping the file handle open until the garbage collection gets around will waste resources. It will also prevent that file being accessed until it's handle is closed. By implementing IDisposable in those cases creates an insurance that once you've finished working with them, they'll be freed up properly.[/quote]
Is the massive List(of T) I have floating around in memory an unmanaged resource?[/quote]
No, it's a managed object. An unmanaged object is something that's not controlled by the Common Language Runtime (CLR). Although the list may use up a bit of memory, using dispose doesn't create any magic for this case. The garbage collection is already well aware of this memory, and will free it when appropriate. As the memory isn't attached to something really important like a file handle, it doesn't require immediate dismissal.

[quote author=NicoQwertyu link=topic=17402.msg177235#msg177235 date=1206299655]The channellist can inherit databasecore, and add a few methods for manipulating the control on the form, and changing icons. The user objects in the channel can derive from Database.User, and just add a few more properties for ping and icon. So it's not a total failure.

Edit: Well, the above is almost true. It's kind of impossible to implement without overriding just about everything, however.  >:([/quote]Essentially, if you want to design your application in a full OOP approach, then you'll want to follow the multi-tier architecture. This means you should separate your user interface, your logic, and your data. In the case of a bot, your data (data tier) would be the Config.ini, userlist.txt, and similar files which store the data. The logic (application tier) would be all the objects that process this data and manage a connection from Battle.net. And your user interface (presentation tier) is the display your user sees, such as the form and listview. This means that you don't want your channel list controlling your database. It should be completely separate, and the logic from the application tier will parse the information to events (or possibly setup delegated functions), and then the presentation tier will deal with displaying this data on the interface.

When a user does something like pressing enter in the chat box, that message should have 'basic' checking done at the KeyPress() level, and then parse it to the application tier (Bot.SendChatMessage(message)) where it will process and handle the rest. By separating these layers, your bot connection is able to be completely independent from the GUI, which may be an option you want to give a user. More importantly, if you want to reuse the bot object in another project, it doesn't have any dependencies tied to the interface of the first project it was created in.

Quickly just revisiting what I mentioned before with User.ChannelInfo.Index. I'd add that property because it's something that would play a part in the majority of programs that use your object. It provides a mean in which they can link a user to a specific number, and when done properly, the user implementing your object will synchronize that with the actual position they have stored in their channel list. This still separates the tiers, but provides a fast way for the presentation tier to match a user from it's channel list to a user in the application tier's userlist.
March 24, 2008, 12:34 AM
Myndfyr
I disagree FrOzeN.  3-tier design doesn't really make sense in this domain; there is very little in terms of "business logic" that is needed for such an approach.

3-tier design conceptually looks like this:

Presentation
====
Business Rules
====
Data

One alternative could be Model-View-Controller.  This provides for interaction between the three tiers, but still cleanly addresses separation of concerns.
March 24, 2008, 4:44 AM
warz
i seriously <3 the mvc approach. its very nice.
March 24, 2008, 1:55 PM
FrOzeN
[quote author=MyndFyre[vL] link=topic=17402.msg177244#msg177244 date=1206333869]
I disagree FrOzeN.  3-tier design doesn't really make sense in this domain; there is very little in terms of "business logic" that is needed for such an approach.

3-tier design conceptually looks like this:

Presentation
====
Business Rules
====
Data

One alternative could be Model-View-Controller.  This provides for interaction between the three tiers, but still cleanly addresses separation of concerns.
[/quote]Whoops, sorry. I completely threw off my terminology. I haven't yet looked much into design patterns (will be doing soon), but what I was implying I think would of been the layered application approach. The 3-tier solution is more distinctly used when the layers are separated for means of scalability (among other reasons).

[EDIT] I had a quick look over the MVC which looks very nice. Just one thing that hit my curiosity, why has MSDN placed it under Web Presentation Patterns opposed to something more generic (being that you'd use this model outside of web-specific implementations)?
March 24, 2008, 3:54 PM
Myndfyr
Microsoft's current focus on MVC is its use in the ASP.NET MVC model, which is why you see it there.  They're building a framework that helps guide MVC development on the web.  I think it, like most frameworks, is kind of a waste (it makes you use their boilerplate instead of building your own that might be more meaningful in the problem domain).  But it's not hard to address SoC in other problem domains - it just kind of is in ASP.NET right now.  In WinForms, for instance, some code I'm working on is very clearly MVC.  I'm writing a bot "core" (the model), and the bot "view" (a controls library), and the bot "controller" (the actual application). 
March 24, 2008, 9:50 PM

Search