Author Topic: FB Graphics #1.2.1: Brief Introduction to OOP  (Read 4500 times)

0 Members and 1 Guest are viewing this topic.

Offline rdc

  • Pentium
  • *****
  • Posts: 1495
  • Karma: 140
  • Yes, it is me.
    • View Profile
    • Clark Productions
Introduction

Object oriented programming (OOP) may seem  bit mysterious, especially for those who come from a procedural background, but in reality OOP isn’t all that different than procedural programming from a coding perspective. You use familiar procedural techniques to implement the code for an object. If you look at the type definition for our screen object, it is basically a standard type definition with some added features. The member functions are also coded much like you would code any procedural function in FreeBasic, the only differences are the name decorations associated with a member function and the scope (visibility) of the function. OOP is really procedural programming plus; it is less about a new way of coding and more abut a new way of thinking.

The Object

In the real world we interact with objects, not pieces of objects. Your car is made up of thousands of individual parts, something we don’t normally think about when we need to drive to the grocery store. We interact with the car as a single object, giving it input, pressing on the accelerator pedal, turning the wheel, and hopefully using the brake pedal at times, and receive an output, the car moves down the road, turns right or left and, (hopefully), stops at the red light. To put it another way, the car presents us with an interface (pedals, steering wheel) and we interact with that interface (press a pedal, turn the steering wheel) and generate an output (the car moves forwards or stops, turns right or left). The car also has certain quantities we need to keep track of such as the level of gas in the gas tank and oil in the engine. The car won’t go without gas, and won’t go very far without oil. When we put together both parts and quantities, we end up with a functioning object called a car.

The Problem Description

Regardless of whether you solve a programming problem with procedural or object oriented techniques, you need a good description of the problem. The word description is intentionally general. A description describes everything that is needed to solve a particular problem, both the algorithms and the data that the algorithms need to use.

Thinking back to the car example, one aspect of a car (let’s call it a property of the car) is movement, going forwards and backwards. To make a car go, we need some motive force, so we will need to add an engine of some sort to the car. To translate that motive force into movement, we will need wheels on the car. You also need some sort of mechanism to start the car in the desired direction and to stop the car once it is moving, an accelerator and brake. Movement implies a quantity, that is, how fast the car is traveling, so it would be nice to have a continuous read out of how fast the car is traveling in a particular direction (to avoid those nasty bugs called tickets), so it would be nice to add a speedometer to the car. Since we want to go backwards as well as forwards, we need a mechanism to switch the direction of the car, so we will add a transmission to the car.

So far all we have done is list a few properties related to the movement of a car, and yet we have a much more clear picture of what we need to do in order to code the movement aspect of a car--and we haven’t written a single line of code.

The description isn’t compete of course. How do we connect the wheels to the car? How do we power the engine? What mechanism do we need to stop the car? What sort of input code do we use in order to change directions? There are a number of things to think about when describing a car, but the very act of listing the properties of a car leads to other properties of a car, all of which give us a more and more complete picture of a car. Once we get to the point that we have a fairly good description of the car, we also have a good blueprint of how to translate the description of a car into code.

Procedural Programming vs. Object Oriented Programming

If we were to take the description of a car and code it procedurally, we would probably take the same approach that the manufacturer does when building a car. We would start with the chassis code, add some engine code, then some transmission code--slowly building up a working model of the car. In the end we would end up with a seemingly unrelated collection of procedures, functions and data items that when taken together would model the car. In other words, a procedural approach to the problem starts with the pieces and builds the car.

To put it another way, procedural programming is centered on how an object works, the algorithms needed to implement the desired functionality of the object. The data structures and supporting functions are designed to implement the algorithmic nature of the solution. This focus on algorithms can present some problems though. Changes in functionality of the object can result in changes to the algorithms, and because of the bottom-up approach to procedural programming, these changes can have a ripple effect through the whole program. As a program become larger and more complex, these ripple effects become more difficult to manage. This is the main reason why object oriented techniques were developed; to manage the growing problem of complexity within procedural programs.

An OOP approach isn’t all that different, we still need to implement the algorithms and data structures that we would in a procedural program, the difference is in how think about and implement those algorithms. By organizing the data and functions that operate on the data into a single entity, we can localize the complexity into a manageable unit. An example will clarify what I am talking about.

Suppose that we have created the code necessary to implement a car, and decide we want to build a racing game. If we had implemented the code procedurally, we are now faced with a problem. The procedural code describes a single car, which means we will need to implement a number of changes to the existing code base to handle multiple cars. This could lead to serious problems if the changes are not carefully thought out and implemented. Even if we had the idea of building a racing game using a procedural method and developed the car code to handle multiple cars, we still run the risk of one car’s executing code affecting another car’s code because the interaction of the different subroutines and functions are visible to the whole program.

With an OOP approach, the changes are much less severe. An object describes a single car, so if we need more than one car, we just create more than one car object. OOP allows us to change the functionality of the program with minimal negative impact. Using objects, one car’s code cannot affect another car’s code, because the executing code is hidden within the object and is not accessible outside the object, except through the object’s interface. This is called encapsulation and encapsulation helps hide the internal  mechanics (which includes the data) of the object from the rest of the program.

Encapsulation and Information Hiding

Encapsulation simply means placing code inside a container and restricting access to that code through an interface. The code inside the container includes both the data defined within the container, and the algorithms that operate on that data. By restricting access to the encapsulated code through a published (visible) interface, we have control over both the input and output of the code, reducing the chance of inadvertent errors.

You are already using objects in your programs, although you may not realize it. The common subroutine (or function) is an example of an encapsulated object. Any variable created inside the subroutine can only be accessed from within the subroutine. If I define an integer called Count inside the subroutine, I cannot access Count from outside the subroutine, because it is invisible to the rest of the program. The scope (visibility) of Count is restricted to the subroutine in which it is defined. This is the principle of information hiding. The restricted scope applies not only to data, but to all the code inside the subroutine. I can’t call a For-Next loop inside a subroutine. In fact, there is no mechanism to do so, enforcing the idea of encapsulation and information hiding.

In order to get data into or out of a subroutine, or function, you need to define an interface. In the case of a subroutine, the interface is the parameter list of the subroutine. The only way to get data into a subroutine (or out of it) is to call the subroutine with the appropriate list of parameters that match the parameter list definition. A function adds an additional element to the interface, the return value. The interface allows you to control both the input and output of the subroutine to ensure that you have good data going into the subroutine and good data coming out of the subroutine.

Suppose your subroutine is intended to draw a point on the screen using an x and y coordinate. Before you draw the point, you can check the coordinate of the point to make sure it is within bounds of the screen, not drawing the point if the coordinate is out of range. You can also rest assured that because the code is encapsulated and hidden from the rest of the program, the check will occur each time the subroutine is called.

In a procedural program, the encapsulation and information hiding is limited to a single subroutine or function. The interaction between the subroutines and functions are visible to the whole program, and can be a source of problems. OOP extends the idea of encapsulation and information hiding to include not only a collection of data, subroutines and functions, it encapsulates and hides the interaction of these subroutines and functions from the rest of the program. The interface of an OOP object isn’t just a set of parameter lists, it defines how the individual members are called and controls the interaction of these members to ensure that the different operations of the object work together correctly and are isolated from outside influences.

Thinking back to our racing game example, the procedural method can be problematic because we must generalize the code to include the interaction of multiple cars. This increases the chances of something going wrong, because all of these interactions are visible to the program as a whole. From an object standpoint, the racing game doesn’t change the interaction going on within the car object, it is hidden from the rest of the program, so we can add car objects to the program without worrying about a affecting the operation of an individual car. We would still need to code the interaction between cars, but we do that through the interface which strictly controls access to the car information and ensures that we are communicating with the car correctly.

Inheritance and Polymorphism

A couple of other buzzwords you have probably heard in relation to OOP are inheritance and polymorphism. These terms relate to the other aspect of OOP that helps to manage complexity, creating reusable code. Reusable code is nothing new of course, procedural programs have been using libraries, a form of code reuse, since day 1 (maybe day 2). Inheritance and polymorphism allow you to not only use existing code, but take existing code and extend or change the functionality of the code for a specific need. This helps manage complexity by allowing us to use an existing wheel (a general wheel not a car wheel, although it could be a car wheel) without having to totally invent the wheel every time we need a change of functionality of an object.

To take the classic example, suppose we have an animal object that can Speak. For a dog the Speak member would Bark and for a cat it would Meow. In a strictly procedural world, we would have to build two different objects, one for the dog and one for the cat. The two objects may be similar in all other respects, so we are doubling the chance of something going wrong when we duplicate code like this. Having two objects also mean we have twice the amount of code to maintain, another source of problems. It makes more sense, and there is less chance for error, if we could just create a single object, Animal and then change the Speak member as needed.

In a fully OOP-capable language, the method would be to define a base object, Animal, that has all the needed animal code, and a virtual member called Speak. A virtual member is like a placeholder; it can change depending on the type of object. We would then define two new objects, Dog and Cat that would be based on the Animal object. The two new objects would contain all the code of the base Animal object, that is, inherit the functionality of the Animal object. Our Dog and Cat now act like animals because they have inherited the animal code, even though we didn’t have to write a specific dog-animal or cat-animal code. Inheritance automatically supplies the animal code of our derived objects. However, the Dog and Cat Speak differently, so after the new objects are defined, we add a Speak method to the Dog that Barks and add a Speak method to the Cat that Meows. The two Speak members are the same as the Speak member in the base animal class, but do different things. The Speak method is said to be polymorphic.

All of this sounds really cool, but unfortunately FreeBasic doesn’t support inheritance. All is not lost however, since we can simulate this behavior using functions pointers. A small example will illustrate what we can do.

Code: [Select]
' FB Graphics Series for DBFInteractive
' Richard D. Clark
' 1.2.1: Simulated Inheritance and Polymorphism
' GPL 3.0
' This program is free software; you can redistribute it and/or modify it
' but WITHOUT ANY WARRANTY; without even the implied warranty of
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
' =======================================================================================================
' This small program illustrates how we can simulate a form of inheritance with polymorphism in FB.
' =======================================================================================================

'Define the Null value.
#Define NULL 0
 
'Animal types.
Enum animaltypes
isdog = 1
iscat
iskangaroo
End enum

'Define our base object.
Type animal
Private:
_anispeak As Sub () 'Virtual member.
Public:
Declare Constructor (what_type As animaltypes) 'The object constructor.
Declare Sub Speak ()'The speak member.
End Type

'Define our polymorphic subs.
Sub DogSpeak
Print "'Woof!'"
End Sub

Sub CatSpeak
Print "'Meow!'"
End Sub

Sub KangaSpeak
Print "'What's up mate!'"
End Sub

'Define our animal constructor.
Constructor animal (what_type As animaltypes)
If what_type = isdog Then
this._anispeak = @DogSpeak
ElseIf what_type = iscat Then
this._anispeak = @CatSpeak
ElseIf what_type = iskangaroo Then
this._anispeak = @KangaSpeak
Else
this._anispeak = NULL
EndIf
End Constructor

'Define our speak command.
Sub animal.Speak ()
If this._anispeak <> NULL Then
this._anispeak ()
Else
Print "'No speak has been defined for me!'"
EndIf
End Sub

'Create three animal objects.
Dim aDog As animal = animal(isdog)
Dim aCat As animal = animal(iscat)
Dim aKangaroo As animal = animal(iskangaroo)
Dim aBird As animal = animal(10)

Print "The dog says ";
aDog.Speak

Print "The cat says ";
aCat.Speak

Print "The kangaroo says ";
aKangaroo.Speak

Print "The bird says ";
aBird.Speak

Sleep

Notice that we have created a single base object, animal, with a virtual member _anispeak, and a constructor that will determine how _anispeak will behave. This virtual member is in the private section of the object definition, because we don’t want to call it directly. We want to make sure that it points to a valid animal speak subroutine, so we will call _anispeak from the public member Speak which will check to make sure _anispeak is not NULL before invoking the associated animal speak subroutine. The peculiar format of the _anispeak sub is the way you define a function (or subroutine) pointer in FreeBasic.

_anispeak is just a placeholder; it will need to be filled in when the program knows the address of the particular animal speak subroutine being called. This assignment is performed in the constructor based on the input of the constructor. If the input to the constructor isn’t in the animal list defined in the Enum animaltypes, NULL is assigned to _anispeak, signaling that an unknown value was used when the constructor was called.

If you run the program you get:

Quote
The dog says 'Woof!'
The cat says 'Meow!'
The kangaroo says 'What's up mate!'
The bird says 'No speak has been defined for me!'

You can see from the output, that each animal has its own speak method even though they are all based on the same object definition. The bird animal doesn’t have a speak method defined, and the object catches that error and let’s us know without crashing the program. The interface we defined for the object has protected us from ourselves.

Summary

While OOP isn’t the cure-all for problems we will face in programming, it does give us tools that we can use to help manage those problems. This introduction only briefly touches on some the main ideas of object oriented programming, a subject that can span a 500 page book. It does provide a context though we can use to examine our screen object in detail, which is the subject of the next tutorial.