04-15-2005, 02:28 AM
I recently came across some books on Design Patterns in software development. The basic idea is that some problems occur again and again so methods to deal with those problems should be debated by the best experts available. The final results should be codified and presented as a "Design Pattern"; the generally accepted best practice way to deal with a recurring design problem.
In this thread I intend to pose some common design problems and what *I* think is the best way to handle these problems. The emphasis is not so much "How to" but "Why to".
If you disagree with my solutions, please feel free to submit your own. That's the only way to improve the state of the art!
04-15-2005, 03:07 AM
Keep track of the last n highest scores.
I recommend storing the high scores in an array. However, since you need persistent storage, you also need to store the values in a text file or INI file or the registry. You could also store the values in a database, although that is likely to be overkill or a more modern method is to store the values in an XML file. Whatever method you choose, I recommend that you load the values from persistent storage into your array when the program starts and save the array to persistent storage when the program ends. Alternatively, you could save the array to storage if the array changes due to a new high score. This will save the data if the program subsequently crashes.
So there are several functions in this solution:
1) load array with saved values (program start)
2) save array (program end)
3) add new score to array (game end)
4) display array (game end)
Since we are saving a name-score combination, it probably is best to use some kind of user defined type rather than 2 arrays or storing the score as a string in a 2D array. Also, the code for loading, saving and displaying the array is somewhat dependant on your program so I won't show code for those functions.
Const MAXSCORES = 10
Public Type Score
Name As String
Value As Integer
Public HiScores(MAXSCORES) As Score
Public Function AddScore(ByVal Name As String, ByVal Value As Integer) as Boolean
Dim i As Integer
Dim tempName As String
Dim tempValue As Integer
Dim changed As Boolean
changed = False
For i = 1 To MAXSCORES
If HiScores(i).Value <= Value Then
changed = True
tempValue = HiScores(i).Value
tempName = HiScores(i).Name
HiScores(i).Value = Value
HiScores(i).Name = Name
Value = tempValue
Name = tempName
AddScore = changed
This function accepts the users name and score. It then attempts to add the data to the array. It goes through the array from start (highest score) to end (lowest score) and compares the current score to the stored score. If it beats the score at that location, it swaps information and continues. This has the effect of INSERTING the high score at the proper location and shifting the other scores down. If there are already the maximum number of scores, the lowest one gets lost.
The function returns a boolean to indicate that the current score was inserted into the array. This can be used to trigger some congradulatory message etc. Alternatively, you could return an integer to indicate what position you got in the array (0 for none).
04-15-2005, 03:31 AM
The usual way to end a program is to close all forms and terminate all running loops and timers. Do NOT use the END statement since this may not properly perform code in form unload events such as saving current states.
Speaking of unload events, all special code in a form that is to execute when you close a form should be placed in the unload event. Do NOT place it in, say, a closeButton_Click() event since the form might be closed by a method other than by clicking the button. Then in the closeButton click event you simply put
and that calls the proper termination code.
It's not often that you have running loops in a program, but they are useful in some event driven programs like games. If you have one, you must end it before closing the program or it will continue running.
Similarily, a timer *might* cause your forms to fail to unload. When you unload a form, any running timers are supposed to stop but if a timer triggers right during the unload event, it could cause the form to reload if it references any controls on that form. To prevent this from happening, simply disable any timers on the form before unloading it.
All loaded forms are added to the Forms collection. You can use this to unload all forms simply by iterating through the collection and calling the unload sub.
04-15-2005, 03:47 AM
Your program runs too fast on newer computers
Implement a game loop that inserts delays that slow down the frame rate to a consistent value.
The loop runs something like this:
1) get the current time
2) check for user input
3) update the game world
4) update the view
5) Check the time again. If not time yet, goto 5
6) goto 1
In step 1, you get the current time and add a fixed interval, like 50ms. This should give you a frame rate of 20 fps. This is now the expiry time.
In step 5, you get the current time and compare it to the expiry time. If the current time is greater, proceed. Otherwise stay in a small loop.
This method is better than simply waitng 50ms at step 5 because the other steps like updating the game world and displaying the view can take a variable amount of time. This method takes that into account.
04-20-2005, 01:23 AM
I just bought a book titled "Visual Basic Design Patterns" by James W. Cooper. The design patterns in his book are more generic (thus more useful) and more object oriented. One big defect of VB6 is the relative lack of OO features like inheritance. Many times I've had similar objects with duplicated code simply because there was no way to subclass an object into sub types.
Or at least I thought that there was no way....turns out that VB6 supports Interfaces, which is a limited form of inheritance.
An interface is like a regular class except that all of it's methods are empty and it contains no data. Other classes can implement the interface and write concrete methods that replace the interface methods. The main benefit here is that implementing an iterface allows an object to be known by that type, as well as it's own. Thus, you can have totally different objects handled the same way by other classes as long as they implement the same interface.
You have a family of similar classes. You have another object that displays or edit those class objects but you have a massive select case statement inside the editor to handle the different object types. Every time you design a new type of object, you have to revise the editor to add it to the list and then recompile.
Create an Interface that contains the common methods of each of the related classes, then have each class implement the interface. The editor now only needs to refer to an object of the interface type, rather than one of the many concrete classes. If new objects are designed, the editor does not need to change as long as the new object uses the same interface.
05-05-2005, 03:39 AM
The Interface Pattern is very important to other patterns in Coopers book and this pattern, the Factory pattern, is an example of this.
You've implemented an interface and have grouped several several different types as using this interface. The problem comes when you have to instantiate one of these different types. Your code has a series of IF-THEN statements to determine which object to create. It works but it looks messy. Furthermore, This code is duplicated in other parts of your program. Finally, adding a new object is messy because you have to find all the other creation code and add it there as well.
Create a special class (the factory) that returns one of those objects (ie. it returns the type specified by the interface). The factory accepts one or more paramters that indicate which type of object to return, or perhaps the factory can decide on its own which type to return. Now all your creation code is in one place and can easily be modified to add new objects.
05-05-2005, 03:53 AM
This one could have saved me a lot of grief had I known about it sooner, although like most programmers, I've used it a lot before unknowingly.
You are writing an application that requires graphing so you add graphing component A to your project. A few days of going over the manual for the component and you understand how A works so you start writing the code.
Later, maybe after the app has been deployed, you are asked to provide new features to the graph. To your dismay, component A does not provide this functionality. So you go out and purchase component B. Now all your graphing code has to be rewritten because B's function calls and procedures are totally different from A's.
Later, B goes out of business (or maybe more features are requested) and you have to go with component C (or maybe back to A). Another rewrite?
You use the Adaptor pattern. Basically, you decide on the ideal interface for your app, not necessarily the interface provided by the component you are currently using or planning to use. Then you write an adaptor class that implements that interface and wraps the component, translating the components functions into the ideal functions. Later on, if you have to use a different component, you simply write a new adaptor that uses the same interface, then plug it in. If necessary, this adaptor can contain several different objects that work together to get the desired ideal interface behaviour.
06-18-2005, 01:37 AM
A while back I had to write a program that displayed data in the form of a graph. This was fairly straight forward but later on, I had to print the graph. The obvious method was to simply use .FormPrint but that was only a stopgap; your typical printer has far more resolution than your monitor (8.5 x 11" x 300dpi = 2550pixels x 3300pixels) so graphs displayed via this method come out looking blocky and jagged.
Obviously I had to draw to the printer using functions like printer.line
Since I had already written the code to draw to the screen, it was simple enough to copy that code and change it so it drew to the printer but later on, maintenance proved to be a big hassel. Any change to the screen code had to be copied to the printer code. Since this was a small program, it wasn't too bad, but in a larger program, spread over several people, there is too much chance that a change in one place might not be copied to the other place.
When I converted to API calls, it actually simplified the code a lot since the API functions simply use handles for drawing. I could pass a handle to a screen object OR a printer object to the drawing code and it would draw it just the same. Sure there were a few differences, but in general, it worked just fine.
Then I had to add code to save the graph as a WMF file.
Again, you can use the api to generate a handle to the WMF file and pass it to the drawing code. The drawing code would then generate the required instructions to be stored in the file. But again, there were differences. For one thing, some font functions didn't work correctly with WMF files as they did with the screen or printer. I had to use a series of IF statements in the code to change the way the drawing code worked depending on what it was drawing to. It was better than 3 separate drawing modules, but it was still a bit complex.
Now that I understand about interfaces though, I can rewrite that code much cleaner than before. I simply use the adapter pattern.
All I would do is design an interface that described the basic drawing methods I require, like lines, text, colors etc. Then I would write 3 objects that implement this interface, one for the screen, one for the printer and one for the WMF file. Finally I would write code that draws a graph on an object of that interface type using the methods defined by that interface.
That way, code specific to an object is contained in that object and not cluttering up my main drawing code. In addition, adding new object types is simplified and the code then becomes much more reusable.
06-18-2005, 02:03 AM
An object has been defined as a self contained package of code and data yet a module contains code and data too. Why isn't it an object? The main reason is that when you add a module to a project, it's code and data are immediately available whereas if you add an object to a project, you have to instantiate it first, that is, create an instance of that object. The kicker is that you can create multiple instances of that object, each with its own set of data. Meanwhile, there is only ever one instance of your module in your application.
Sometimes though, you only *want* one instance of your object. Maybe you have a security manager object, you wouldn't want multiple copies of that! Or perhaps you have an object that controls a unique resource like the registry. For example, VB has one (and only one) printer object and one screen object.
You want to ensure that a given object only instantiates a single instance of itself and that this instance is globally available.
Use the Singleton pattern. The basic idea is to add code to the initialize method that checks to see if an instance of this object has already been instantiated, if not, create itself, it so, return the previous instance.
In other languages, this can all be done within the object itself but in VB6, we require outside help in the form of a module.
Public ObjectCounter as Integer
Public glbObject as ObjectClass
Private Sub Class_Initialize()
If ObjectCounter = 0 Then
Set glbObject = me
ObjectCounter = 1
Public Function GetObject() as ObjectClass
Set GetObject = glbObject
When you use the object, you use the GetObject function to return the legal instance.
06-18-2005, 02:43 AM
Even though we try to write object oriented code, the underlying computer tends to be procedure oriented in that the program starts in a single place, follows a set of instructions, then exits. In the course of execution, the code typically encounters numerous branches where the code can take different paths.
In general, it is better to reduce the number of available paths in order to reduce your code bugs. That's why code duplication should be avoided whereever possible.
A control point is a place in your code which HAS to be run by the computer. It cannot be bypassed. As a rule, you should include as many control points as you can. They force you to consolidate your code and reduce branches. For instance, the Singletons and Factories mentioned above are control points since they control access to their respective classes.
As an example, let's look at one of my pet peeves; the END statement.
A program begins with at a single point and should end at a single point. Usually that happens when the last form is unloaded. Suppose you write a simple program like this and your boss asks you to add a button to Quit. Since you are in a hurry, you add a button and simply put the END statement in the click event.
Later on, usage testers request that you add a dialog to confirm quitting. This is fairly simple but you have to add it in two places, once in the quit button and once in the form unload since some users still close the window to exit the program.
Still later, you go on vacation. While you are away, a request comes in to change the wording of the confirmation message. Since it is a simple request, your coworker does it to the form unload code but doesn't realize that there is duplicate code in the quit button event.
When you get back the testers are reporting this as an intermittent bug (the worst kind) since some of them are using the quit button and some aren't.
Now this is a simple program and it isn't too hard to run through the code and fix bugs like this. But if it was a huge program, written by a team of a dozen people, bugs like this might be very hard to find.
Instead of using the END statement, it would have been much simpler to treat the Form_Unload event as a control point and simply pass execution there. In the click event, you simply put:
Which triggers the unload event. This gives you a single place where you add confirmation code, save code, resource closing code etc. etc. and prevents code duplication.
07-05-2005, 12:20 AM
a) You have written a graphical editing program that adds, modifies and deletes various shapes. You have written it as a collection of different shape objects that get instantiated or modified in response to menu options or mouse clicks. It all works as expected but now you are asked to implement an undo command.
b) You have written a home automation program that controls lights, heat and the security system. It is organized as a set of objects representing various devices. Now, however, your boss has asked you to implement a scripting function.
c) You have a large, complex application with GUI components including a complete set of menus, toolbars and other controls. The functionality of the menus is duplicated in the toolbars as well as some other places. This code duplication is causing maintenance headaches.
d) You have written a front end for a database application that organizes and edits a large database. During initial testing, the database sat on your developement system and everything was fine. Now further tests have placed the database at a busy remote location and your client is locking up waitng for access and for the server to process its tasks.
In all of these scenarios, the problem is that the GUI is too closely coupled to the objects that carry out the directions of the GUI.
Decouple the GUI from the action objects. Instead of having the GUI call the action objects directly, it creates Command Objects that are placed on a queue. A command engine then goes through the queue and executes each command as required.
a) Since all the commands have been stored in a queue, it is easy implement an "undo" command by stripping the last command from the queue, clearing the screen, then running all the commands again.
b) Scripting is automatic. All you need to add is an interface to create the scripts and assign times to the commands.
c) Each menu or toolbar item calls a command object. This puts all the actual code in the command object instead of the GUI. Code is only written once.
d) Since the GUI simply places commands on a queue without waiting for execution, it operates asynchronously. This allows the server to process commands at its own pace and the GUI client is not locked waiting for the server to respond.
In its simplest form, the command pattern can simply be a collection of strings (commands) that are parsed by the command engine to perform certain tasks. A more complex method might use UDTs to contain a string for the command plus a date for activation time. An even more complex method can use actual classes both to contain command data as well as command methods. For instance, you could send a sort command to a database server that contains the method you wish to do the sorting.
07-17-2005, 01:30 AM
TeraBlight was kind enough to write several example programs that demonstrate the patterns that I've been describing. These examples demonstrate the Adapter Pattern, the Factory Pattern (plus the Singleton Pattern since many factories are designed to be singletons) and the Command Pattern. Here they are: