Object Oriented Programming (OOP)
Delphi is an Object Oriented Programming language. What this means is that the applications you create work because you create objects that interact with each other.
An object is merely a 'thing' in your program. A form is an object. An Edit box is an object. A button is an object.
Think of a comparison to our classroom. What are the objects in our classroom? First of all, there's the classroom itself (just like a form). Then within the classroom object, we have whiteboard, desks, chairs, students, teacher, whiteboard markers, computers etc. So an object may contain other objects.
Now lets look closer at our student object. The student object has many different properties, such as skin colour, hair length, hair colour, gender, vocal octave, nose style, eye colour, name, age, musical taste… you get the idea. A property is a characteristic of an object. A button may be 20 pixels wide, or have the caption 'Press me'. These are all values of the object's properties.
(Object.Property := Value)
Button1.Width := 20;
Button1.Caption := 'Press me';
The most important property of an object is its name. An object's name uniquely identifies that object within your program so you can refer to it by name and know exactly which object your dealing with. That's why when you create objects, they start with Button1, Button2, etc. Best to change the name to something that describes what the object is/does, such as CalculateButton, RadiusEdit, or HumanCheckbox.
Two objects within the same form cannot have the same name. However, if you have two different forms within one project, each form can contain an object that has the same name as an object in the other form. For example, you may have two forms within your project, one for calculating area of a circle, the other for calculating area of a triangle. Each one could have its own CalculateButton object. The reason this is ok is they are within different objects (each form is a different object, and each has its own set of objects 'inside' of it). Say our forms were AreaOfCircleForm and AreaOfTriangleForm, here is how we could technically refer to them:
AreaOfCircleForm.CalculateButton.Visible = True;
AreaOfTriangleForm.CalculateButton.Visible = True;
When changing an object's properties within your program code, you must refer to it the following way:
objectname.property := value;
Always. For example, say you wanted to set to True the visible property of the CalculateButton object:
CalculateButton.Visible := True;
If you are modifying the object from within the same form (most of the time you will be) you don't need to have AreaOfCircleForm at the beginning of your statement.
Don't forget to include the property there - you can't just type CalculateButton := True because you are not assigning the value to the object itself, you must assign it to one of its properties (in this case, the visible property).
Objects also have events. An event procedure is triggered when something happens such as the user clicking on a button, changing an Edit box, even hovering the mouse arrow over a component (a component is another word for an object on a form).
Variables
What is a variable? A variable is of course a placeholder within a program whose value can change as the program is running. Just like in algebra, we substitute the value with a coded identifier (in algebra we use x, y, z etc, in programming we can be more descriptive - time, count, total_of_scores).
Unless explicitly stated by you, each procedure's variables are kept private, that is, they cannot be accessed outside of the procedure in which they are declared. This can be frustrating at times. However, it's for the best - if you have more than one instance of the same class (eg two different students) you need to be able to use the same variable names for each (eg eye_colour) without one overwriting the other. Just like properties of components have the same names (caption, text, alignment) - because they are private, changing a property of one button does not affect the same property in all buttons.
Before storing a value in a variable, you need to tell Delphi to set aside some memory space for the variable. You do this by declaring the variable at the beginning of the procedure (just before the begin statement) within which the variable will exist. You use the following syntax:
var variable_name : type;
var is Delphi's declaration keyword, variable_name is what you are calling the variable (make it descriptive, and it must begin with a letter and can contain numbers or an underscore if desired), and type is the type of variable you are declaring. So far in class we have declared variables of the type real which means real number (decimal values). There are other variable types, such as char, integer, string, boolean… investigate for yourself what these types are.
Lets look at a few examples:
var DVD_title : string;
var count : integer;
var IQ_person1 : real;
You can also declare more than one variable in the one statement, if they are the same type, by separating the variable names with commas. For example:
var IQ_person1, total2, percent_of_maximum: real;
You declare constants (values that do not change whilst the program is running) the same way, but use the const keyword:
const tax_rate : real;
Question: Why use constants at all? Why not just use a variable? Good question. If you're stuck for the answer, remember why Delphi is so strict.
Converting a string to a number and vice-versa
During our programming endeavours, we have discovered that the values in our 'caption' and 'text' properties, even though we might enter numbers, are actually stored as strings. We found this out the hard way - when we tried to run our programs that perform calculations on values entered by the user, the program crashed. To solve this problem, we made use of some delphi functions called StrToFloat() and FloatToStr().
The StrToFloat() function converts a string value (parsed into the function by putting it inside the parentheses) into a floating point decimal number, enabling us to perform a calculation on it:
area := StrToFloat(AreaEditBox.Text);
converts the contents of the AreaEditBox.Text property to a floating point decimal number, enabling us to calculate with it.
The FloatToStr() function converts a numeric value (parsed into the function by putting it inside the parentheses) into a string value, enabling us to put it into a caption or text property (or perform other cool string operations such as concatenation).
DisplayLabel.Caption := FloatToStr(area);
assigns the value of the variable area to the caption in the DisplayLabel.
AreaString := FloatToStr(area);
DisplayLabel.Caption := AreaString;
does the same thing, it just assigns it to the AreaString variable first. Which method you choose depends on the permanance of the AreaString variable - eg if you are going to use it again for another purpose (eg putting the value into a different caption, or join it together with another string) you might choose the second method.
Selection: IF… THEN… ELSE
This bit is to be read in parallel to the algorithms bit on selection - this is the Delphi code, the other bit covers the theory.
Ok, so far we've looked at some pretty sequential programs - ie, read -> calculate-> write. Pretty boring, but at least they're finite. Kind of like reading a book - page by page, you make it towards the end eventually.
Then somebody invented the 'choose your own adventure' type book, where you could go this way or that. In programming, that's the way selection works - allowing the program to make a decision to go this way or that, based on the result of a boolean (TRUE/FALSE) condition.
Syntax:
IF condition THEN execute this action ELSE execute alternative action;
Basically we're checking to see if condition is TRUE. Condition might be count=100, answer='yes', age>18… basically anything that has a single TRUE or FALSE answer. If the condition is met, then execute this action is carried out. If not, then execute this alternative action is carried out.
You don't even need to have the ELSE part in it - if you leave it out, then the alternative action is that nothing happens.
Examples.
if ScorePercent = 100 then ScoreLabel.Caption := 'Perfect Score. Congratulations!';
if not AGE < 17 then message:= 'OK, old enough… proceed' ELSE message := 'Sorry, too young';
MessageLabel.Caption := message;
Example 1 demonstrates two things: firstly that for a condition or comparison, you use =, not := (:= is used to assign a value); secondly that you don't need to have the ELSE in every IF.
Example 2 demonstrates two things, firstly that an if statement does not just deal with equal comparisons, it can be greater than, less than, greater than or equal to, less than or equal to; secondly that a NOT reverses the outcome of the condition.
Interestingly, if your condition deals with a boolean variable type then you have two possible values - TRUE or FALSE. That means that instead of writing your condition as IF variable = TRUE you can just write IF variable as a shortcut. Similarly with IF variable = FALSE and IF NOT variable. Cool, huh?
How would you incorporate OR or AND into your conditions?
IF condition1 OR condition2 OR condition3 OR condition4 THEN execute this action ELSE execute alternative action
IF condition1 AND condition2 AND condition3 AND condition4 THEN execute this action ELSE execute alternative action
What would be the output if only two of these conditions are met? Here's a hint - remember back to logic gates - they work the same way! See? Everything ties in together in IPT!
Selection: CASE… OF
We can use an alternative to the IF… THEN… ELSE statement, if we have heaps of possible values to check for.
Example.
var message : string;
var age : integer;
begin
_ age = AgeScrollBar.Position;
_ case age of
_ _ 0: message := 'embryo';
_ _ 1: message := 'baby';
_ _ 2..4 : message := 'toddler';
_ _ 5..18 : message := 'schoolie';
_ _ 19..65 : message := 'slave to the machine';
_ _ 66..100 : message := 'retired';
_ _ else message := 'why are you still alive?';
_ end;
_ MessageLabel.Caption := message;
end;
Ignore the underscores at the beginning of the lines (I don't know how to indent)… But be sure that your code has the appropriate indents.
The restriction we have with CASE… OF is that we can only really deal with integer (whole numbers) or char (single character) variable types. But you can test against a list of values, separated by commas (note the range in the middle, too):
19, 20, 21, 30..35, 40, 41 : message := 'you are a lucky age';
And lastly, as soon as one condition is met, all of the remaining ones are ignored. So you won't double up on actions if the value matches more than one condition (so ensure you sequence your checks correctly if you have this situation).
Iteration: DOWHILE
Read these bits on iteration in parallel with the iteration bits in Algorithms and Design page.
DOWHILE is an example of a pretested loop - the condition is at the beginning, so only while the condition is met will the loop run. Technically, it might not run at all, if the condition is not met the first time.
Example.
procedure TForm1.Button1Click(Sender: TObject);
var total,count: integer;
begin
{Counting in squares, until you reach a number over 1 million}
total := 0;
count := 0;
WHILE total <= 1000000 DO
_ begin
_ total := total + (count * count);
_ memo1.Text := memo1.Text + inttostr(count) + ' ' + inttostr(total) + ', ';
_ count := count + 1;
_ end; {end the begin and therefore the loop}
end; {end the procedure}
This procedure will count upwards from 0, and will add the number's square (ie multiply by itself) to a separate running total. While the total is less than one million (regardless of count, the loop will continue.
Points to note: Beware NOT to put a semicolon at the end of your DOWHILE line…. lest your loop does nothing (when it reaches a semicolon, it thinks it's reached the end already). So unless there is only one action in your loop, you'll need to use a begin… end, because as soon as it gets to a semicolon (;) that is not in the middle of a begin…end sequence, it interprets that as the end of the loop and goes back to the WHILE for another iteration.
Note the indentation - the end should line up with the begin to make it neat and tidy. Technically you could argue that the semicolon (after the end that completes the loop) should be on a line of its own, to line up with the WHILE. Good call. I'm with you on that. Fight the power.
Iteration: REPEAT…UNTIL
A REPEAT… UNTIL is a little different in that you don't have the semicolon woes that the DOWHILE creates. Oh, and it's a post-tested loop, which means that the condition is at the end of the loop, forcing it to execute at least once.
Think of that difference between a DOWHILE and REPEAT UNTIL this way: You buy a coke and need to check if you've won a prize. If it's printed under the lid, you can simply check under the lid BEFORE drinking all the choke (pretest - DOWHILE). But if it's printed at the bottom of the bottle, you can only check if you've won AFTER you drink the stuff (post-test - REPEAT…UNTIL). What a useless analogy.
Example.
{variable declarations omitted}
REPEAT
_ LottoNumber1 := random(45)+1;
_ LottoNumber2 := random(45)+1;
UNTIL LottoNumber1 = LottoNumber2;
ShowMessage('Numbers are equal, Sire');
This simple section of code will keep on generating two random numbers until they are equal, then display a message.
Iteration: FOR… TO… DO
Last of the loops now. If you already know how many times you want to repeat, use a FOR TO DO loop. It will iterate the number of times that you specify.
Example.
{get bigger}
FOR ImageSize := 1 TO 500 DO
_ begin
_ Avatar.Height := ImageSize;
_ Avatar.Width := ImageSize;
_ end;
{get smaller}
FOR ImageSize := 500 DOWNTO 50 DO
_ begin
_ Avatar.Height := ImageSize;
_ Avatar.Width := ImageSize;
_ end;
This section of code makes the object called Avatar get bigger by starting as a 1 x 1 square and counting all the way to a 500 x 500 square (FOR TO DO, total 500 iterations). Then, it gets smaller again, starting as a 500 x 500 square, finishing as a 50 x 50 square (FOR DOWNTO DO, total 451 iterations). Yup, downto will count backwards for you.
Remember what I said about the semicolon right at the end of the WHILE…DO line? Same story here, I'm afraid. If you put a semicolon immediately after DO, your loop is empty and meaningless. And as with the WHILE DO, here you need to use a begin..end if you want more than one line of code.
The Software Development Cycle
We've covered the DDE cycle previously, which is a very generic way to plan a solution to solve ANY problem, programming or otherwise. When you live, breathe and poop program code, it helps to have a more task-specific framework to follow. Enter the Software Development cycle, a tried and tested method of software design and development, used widely in the industry.
We have read the textbook's spiel on the SDC (pages 42-47) and it is summarised in this document. You need to fill in the blanks with a suitable word or phrase. It's not verbatim, so in some cases you won't find the exact wording in the text.
In a nutshell, the SDC follows the following steps, and in this order.
- Define the problem
- Specify the solution
- Design the solution
- Implement the solution
- Test the solution
- Evaluate the solution
Throughout the whole process, you must be documenting - it is not a separate step, rather a necessary part of each step.
The solution is not just the program you write. The solution is a combinatoin of product and process. The program is only part of the solution. The solution also entails deadlines, documentation, costs, schedules and timelines, the meetings… all that stuff. When you evaluate, it's not just evaluating the product, you also evaluate the process. Comprende?
You'll find my answers to the above summary here.
Problem Definiton
In order to clearly define the problem and meet the expectations of your client (client being the person asking you to solve the problem), you will probably need to ask questions to clarify exactly what is required. For example, suppose you are asked:
I need a program to allow me to calculate mathematical formulas.
Can you simply go ahead and produce what this person is asking? Well, you could… but it's highly likely that your program will not meet their expectations or needs. So communication helps to define the problem. Ask questions, and lots of them. Such as:
- What formulas do you want to include in your program?
- Who will be the end user?
- What will they be doing with the output?
- Why do you/they require this program?
- How permanent will the software be (ie will it be used once or twice and then abandoned, or will you still be relying on it in 5 years time)?
- Will the software need to be adaptable (ie will it need to change in the future)?
- When is the software required?
Questions such as these help the problem definition to evolve into something that gives both parties a clearer understanding of what we are trying to solve.
Solution Specification
In the solution Specification phase, you are giving your client a clear picture of what your solution will be. The emphasis is on what because they probably won't be interested in how you are going to solve their problem, they just want it done. Where will we be at the end of the project? What outputs will the program produce? What timeframe are we giving? What help and documentation will the user have a their disposal?
Remember, the solution is not just your program, it's the entire package - program, process, documentation.
Solution design
Solution design is where you start thinking about how your project will work. What processing is required to turn the inputs into outputs? You write your algorithms (as flowcharts, pseudocode or Nassi-Schneidermann diagrams), plan your user interface (your screens and forms). For a large project you would go to the extent of selecting your team and resources, and allocating work.
Implementation
This phase is where you create your end solution. Write your code, create your forms, write your documentation. This is where the programmer becomes involved, to bring the solution to life. As a programmer, you will write program code using the algorithms designed in the prior phases. Do NOT deviate from the algorithms! Remember - no assumptions!
Testing
The fun part, some would argue. The frustrating part, others would argue. Probably a bit of both.
Let's look at a couple of testing methods.
Desk Check
You can use a desk check to test your algorithms. A desk check entails listing your variables as column headings in a table, and stepping through your algorithm line by line. It is very useful for logic-checking, such as checking if you have your instructions in the correct sequence.
Take a look at this example algorithm:
Desk_Check
x = 1
y = 3
DO WHILE x < 5
y = y + x
x = x + 1
END DO
total = y * x
x = 0
y = y + 1
END
First, list your variables as column headings. Where you have constants, it would be worth including them too (just in case you change the value of them without realising it!).
| instruction | lines executed | x | y | total |
Then, add your algorithm, line by line and the values of each variable after each line is executed.
| instruction | lines executed | x | y | total |
| x = 1 | 1 | 1 | - | - |
The first two columns, instruction and lines executed are simply markers. I've added each line of code here in full, however all you need is something to help you keep track.
As you can see, after the first instruction is executed, the value of x becomes 1, and the variables y and count remain uninitialised.
| instruction | lines executed | x | y | total |
| x = 1 | 1 | 1 | - | - |
| y = 3 | 2 | 1 | 3 | - |
| DO WHILE x < 5 | 3 | 1 | 3 | - |
| y = y + x | 4 | 1 | 4 | - |
| x = x + 1 | 5 | 2 | 4 | - |
Notice that only one variable changes with each instruction. For example, the new value of y becomes the current value of y (from the previous line) added to the current value of x (from the previous line). The values of the others do not change. Well written algorithms modify only one variable in an instruction.
When I get to an END DO (or an END IF, or the REPEAT part of a REPEAT UNTIL), I tend not to include that as an instruction because nothing is happening. This is different to a DO WHILE or an UNTIL, where a comparison is being made, so I tend to count them. Up to you.
So of course, in a loop, you're repeating sequences of instructions.
| instruction | lines executed | x | y | total |
| x = 1 | 1 | 1 | - | - |
| y = 3 | 2 | 1 | 3 | - |
| DO WHILE x < 5 | 3 | 1 | 3 | - |
| y = y + x | 4 | 1 | 4 | - |
| x = x + 1 | 5 | 2 | 4 | - |
| DO WHILE x < 5 | 6 | 2 | 4 | - |
| y = y + x | 7 | 2 | 6 | - |
| x = x + 1 | 8 | 3 | 6 | - |
| DO WHILE x < 5 | 9 | 3 | 6 | - |
| y = y + x | 10 | 3 | 9 | - |
| x = x + 1 | 11 | 4 | 9 | - |
| DO WHILE x < 5 | 12 | 4 | 9 | - |
| y = y + x | 13 | 4 | 13 | - |
| x = x + 1 | 14 | 5 | 13 | - |
| DO WHILE x < 5 | 15 | 5 | 13 | - |
As x is no longer less than 5, we exit the loop at this point.
The final three lines of the desk check are as follows:
| instruction | lines executed | x | y | total |
| total = y * x | 16 | 5 | 13 | 65 |
| x = 0 | 17 | 0 | 13 | 65 |
| y = y + 1 | 18 | 0 | 14 | 65 |
Desk checking can be a good idea if you cannot figure out why a variable has a different value to what you expect it to have. You can of course use it for program code as well.
Borland's Delphi offers an easier way to keep track of variables within your program, called a watch list. You specify the variables you want to watch as your program executes, and the current values of the variables are displayed in a special window called the watch list. Go to Run menu, and select Add Watch. Then type your variable name, or object.property from the object inspector (perhaps a button is disappearing at the wrong time?) and click ok. Then run your program line by line using Trace Into, Run to Cursor, Step Into in the run menu. Figure out what each of these do (or use the help menu!).
Another alternative is to output the values of variables at certain points within the program, so that you can keep an eye on it as you go. You might create a special label, or use a pop up message box (showmessage).
Debugging has been recognised as a painful process, so many programming languages, such as Microsoft's Visual Basic have made it easer to debug programs. In addition to being able to run programs line by line, checking the values of variables can be as easy as hovering your mouse cursor over the variable name in the program code - the value comes up as a tooltip! Very swish!
Test Cases
Another way to test your program is to use test cases. Obviously the algorithm above does not have any inputs (no user input, no file input) so there is no need for a set of test cases. But what if the user is required to enter the value of x, instead of x incrementing by one?
Test_Cases
x = 1
y = 3
DO WHILE x < 5
y = y + x
READ x
END DO
total = y * x
x = 0
y = y + 1
END
We would want to test what happens here if x is less than 5, equal to 5, and greater than 5, just to make sure that the loop executes correctly. We may also want to test a negative value of x, or a very high value of x, to see if our outputs end up the way they should (imagine we output the values of total, x and y at the end).
Now, a formal test case set wouldn't be required for the above program, but what if there were more inputs? With 20 inputs, for example, a big program could have many errors that many tests cases would identify.
For example, suppose you had a game where the player is stuck in a cave with a killer rabbit (sound familiar?). The cave is an 8 x 8 grid and each time the player moves one square, the rabbit moves one square. If the rabbit is in the same row or column as the player, the the rabbit 'sees' the player and starts to move towards them (still taking it in turns). When the rabbit lands on the same square as the player, the game is over. There are three hidden 'bottomless pits' in the grid, so the player can 'lure' the rabbit over one of the pits to its death and win the game. Alternatively, if the player lasts 30 turns, the rabid rabbit becomes a vegan and is no longer interested in 'consumin' human'.
What are the conditions? Are the rabbit and player squares the same? Are the rows the same? Are the columns the same? Is the player at an edge of the grid? Is the rabbit at an edge? Is the player on a pit? Is the rabbit on a pit? Have 30 turns been and gone? Probably many more.
So what test cases?
Here are some possible situations that might have unexpected results and therefore should be tested before releasing the game to the public:
| Test No | Test Description | Player position | Rabbit position | Pit 1 | Pit 2 | Pit 3 | Turn | Expected Result | Actual Result | Cause | Fix |
| 1 | Player moves into square (1,1) from square (2,1) | (1,1) | (5,6) | (3,3) | (5,7) | (2,1) | 3 | Buttons to move north and west should be disabled | As expected | ||
| 2 | Rabbit is on same row as player | (3,7) | (3,1) | (3,3) | (5,7) | (2,1) | 23 | Rabbit's row should not change in next move, only column | Row changed, but column stays same | ||
| 3 | Rabbit is in square next to player, but it's already turn 30 | (3,7) | (3,1) | (3,3) | (5,7) | (2,1) | 23 | Rabbit should turn vegan and player wins message pops up | Game does not end, instead goes to turn 31 and rabbit eats player instead of carrots |
That last one tests a combination of borderline situations - which is often the case in testing - more than one thing may need to be tested at a time. Or, unrelated things can be tested at the same time - combining tests can save much time and money.
BTW, the Cause and Fix columns would be used by the programmer to keep a record of how the problem was fixed.
I'll leave you to dream up some more scenarios for the above. In your own test schedule, you'll have heaps of test cases - not just two or three.
In the real world of system testing, there are huge expensive multiuser databases used for recording such test cases and results. Such programs allow the ability to assign a test case to people (such as a programmer, user acceptance tester, test manager) and assign priorities (such as Business Critical, High, Low) so that decisions can be made as to when the problem should be fixed. A log can be kept of the bug's history, which is useful for researching the cause of the bug and trying to come up with a solution. It's a fascinating thing to be a part of, but frustrating when something that is causing you extra mundane work is deemed a low priority because there are more important problems to fix!