Getting Started with OOP
From mechanics to concepts
At this point, this tutorial lesson takes a major
change in direction. Up to this point, we have been concerned with
the mechanics of getting you ready to write, compile, and execute C# programs.
Now we are going to switch our attention to the concepts of OOP.
In order to successfully program using C#, you must understand both the
mechanics and the concepts.
Purpose of the miniseries
As mentioned earlier, I will describe and discuss the necessary and
most significant aspects of OOP using C# in this miniseries. I will
make no attempt to teach you all of the subtle nuances of C#. Rather,
I will strive to teach you only what you really need to know to understand
how C# implements object-oriented programming.
More detailed material on the many features and nuances of C# are provided
in other publications by myself and others. Once you have completed
this miniseries, you should be capable of learning the subtle nuances of
C# from other sources (including other tutorial lessons that I will
publish outside the scope of this miniseries).
The three pillars
Most books on OOP will tell you that in order to understand OOP, you
need to understand the following three concepts:
-
Encapsulation
-
Inheritance
-
Polymorphism
I agree with that assessment. (Some books will also add abstraction
and/or late binding to the list. I tend to think of these two topics
as being included in one or more of the three concepts listed above.)
Begin with encapsulation
Generally, speaking, these three concepts increase in difficulty going
down the list from top to bottom. Therefore, I will begin with Encapsulation
and work my way down the list in successive lessons.
What is an Object-Oriented Program?
Many authors would answer this question something like the following:
An Object-Oriented Program consists of a group of cooperating
objects, exchanging messages, for the purpose of achieving a common objective.
What is an object?
An object is a software construct that encapsulates data (into
a software entity), along with the ability to use or modify that data.
What is encapsulation?
An interesting description of encapsulation was recently given in an
internet.com
article By Rocky Lhotka regarding VB.NET. That description reads
as follows:
"Encapsulation is the concept that an object should totally
separate its interface from its implementation. All the data and implementation
code for an object should be entirely hidden behind its interface.
The idea is that we can create an interface (Public methods in a
class) and, as long as that interface remains consistent, the application
can interact with our objects. This remains true even if we entirely rewrite
the code within a given method thus the interface is independent of the
implementation."
I like this description, so I won't try to improve on it. However,
I will try to illustrate it in the paragraphs that follow.
A real-world analogy
Abstract concepts, such as the concept of an object or encapsulation,
can often be best understood by comparing them to real-world analogies.
One imperfect, but fairly good analogy to a software object is the radio
in your car.
(This is not the first time that I have used the radio analogy
when explaining object-oriented programming. If this analogy sounds
familiar to you, that probably means that you have read one of my earlier
articles on OOP involving some other programming language.)
The ability to store data
Your car radio probably has the ability to store data, and to allow
you to use and modify that data at will. (However, you can only
use and modify that data through use of the operator interface that is
provided by the manufacturer of the radio.)
The data that can be stored in your car radio is a list of several frequencies,
which correspond to the frequencies of your favorite radio stations.
Using the stored data
The radio provides a mechanism (user interface) that allows you
to use the data stored therein.
When you press one of the frequency-selector buttons on the front of
the radio, the radio automatically tunes itself to the frequency corresponding
to that button. (In this case, you, the human user object, are
sending a message to the radio object asking it to perform a particular
action.)
If you have previously stored a favorite frequency in the storage location
corresponding to that button, pressing the button (sending the message)
will normally cause the radio station transmitting at that frequency to
be heard through the radio's speakers.
If you have not previously stored a favorite frequency in the storage
location corresponding to that button, you will probably only hear static.
(That
doesn't mean that the radio object failed to respond correctly to the message.
It simply means that its response was based on bad data.)
Modifying the stored data
The operator interface also makes it possible for you to store or modify
those frequency values. This is done in different ways for different
radios. On my car radio, the procedure is:
-
Manually tune the radio to the desired frequency
-
Press one of the buttons and hold it down for several seconds.
When the radio beeps, I know that the new frequency value has been stored
in a storage location that corresponds to that particular button.
Please change your state
What I have done here is to send a message to the radio object asking
it to change its state. The beep that I hear could be interpreted
as the radio object returning a value back to me indicating that
the mission has been accomplished. (Alternately, we might say
that the radio object sent a message back to me.)
We say that an object has changed its state when one or
more data values stored in the object have been modified. We also
say that when an object responds to a message, it will usually perform
an action, change its state, return a value, or some combination of the
above.
Please perform an action
Following this, when I press that button (send a message), the
radio object will be automatically tuned to that frequency.
If I drive to Dallas and press a button that I have associated with
a particular radio station in Austin, I will probably hear static.
In that case, I may want to change the frequency value associated with
that particular button. I can follow the same procedure described
earlier to set the frequency value associated with that button to
correspond to one of the radio stations in Dallas. (Again, I would
be sending a message to the radio object asking it to change its state.)
Jargon
As you can see from the above discussion, the world of OOP is awash
with jargon, and the ability to translate the jargon is essential to an
understanding of the published material on OOP. Therefore, as we
progress through this series of lessons, I will introduce you to some of
that jargon and try to help you understand the meaning of the jargon.
Persistence
The ability of your car radio to remember your list of favorite stations
is often referred to as persistence. An object that has the
ability to store and remember values is often said to have persistence.
State
It is often said that the state of an object at a particular
point in time is determined by the values stored in the object. In
our analogy, even if we own identical radios, unless the two of us have
the same list of favorite radio stations, the state of your radio object
at any particular point in time will be different from the state of my
radio object.
(However, it is perfectly OK for the two of us to own identical
radios and to cause the two radio objects to contain the same list of frequencies.
Even if two objects have the same state at the same time, they are still
separate and distinct objects. While this is obvious in the real
world of car radios, it may not be quite as obvious in the virtual world
of computer programming.)
Sending a message
A person who speaks in OOP-speak might say that pressing one of the
frequency-selector buttons on the front of the radio sends a message
to the radio object, asking it to perform an action (tune to a particular
station).
That person might also say that storing a new frequency that corresponds
to a particular button entails sending a message to the radio object
asking it to change its state.
Invoking a method
C#-speak is a little more specific than general OOP-speak. In
C#-speak, we might say that pressing one of the selector buttons on the
front of the radio invokes a method on the radio object. The
behavior of the method is to cause the object to perform an action.
As a practical matter, the physical manifestation of sending
a message to an object in C# is to cause that object to execute one of
its methods.
Similarly, we might say that storing a new frequency that corresponds to
a particular button invokes a setter method, or a mutator
method, on the radio object.
(In an earlier paragraph, I said that I could follow a specific
procedure to set the frequency value associated with a button to correspond
to one of the radio stations in Dallas. Note the use of the words
set and setter in this jargon. Note also that C# has a slightly different
slant on the concept of set than does either C++ or Java. I will
explain this different slant in a subsequent lesson.)
Behavior
In addition to state, objects are also said to have behavior.
The overall behavior of an object is determined by the combined behaviors
of its individual methods.
For example, one of the behaviors exhibited by our radio object is the
ability to play the radio station at a particular frequency.
When a frequency is selected by pressing a selector button, the radio knows
how to translate the radio waves at that frequency into audio waves compatible
with our range of hearing, and to send those audio waves out through the
speakers.
Thus, the radio object behaves in a specific way in response
to a message asking it to tune to a particular frequency.
Where do objects come from?
In order to mass-produce car radios, someone must first create a set
of plans, (drawings, or blueprints) for the radio. Once the
plans are available, the manufacturing people can produce millions of nearly
identical radios.
A class definition is a set of plans
The same is true of software objects. In order to create a software
object in C#, it is necessary for someone to first create a plan.
In C#, we refer to that plan as a class or a struct.
(I'm going to ignore the existence of structs at this point in the discussion,
because they aren't needed to understand the concepts at this level.)
The class is defined by a C# programmer. Once the class definition
is available, that programmer, (or other programmers), can use it
to produce millions of nearly identical objects.
(While millions may sound like a lot of objects, I'm confident
that during the next few years, C# programmers around the world will create
millions of objects using the standard C# class named Button.)
An instance of a class
If we were standing at the output end of the factory that produces car
radios, we might pick up a brand new radio and say that it is an instance
of the plans used to produce the radio. (Unless they were object-oriented
programmers, the people around us might think we were a little odd when
they hear us say that.)
However, it is common jargon to refer to a software object as an instance
of a class.
To instantiate an object
Furthermore, somewhere along the way, someone turned the word instance
into a verb, and it is also common jargon to say that when creating a new
object, we are instantiating an object.
Comparison with Java
All of the object-oriented concepts discussed thus far apply equally
well to either C# or Java. Thus, by learning how to write object-oriented
programs using C#, you are also learning quite a lot that applies equally
well to writing object-oriented programs using Java.
A little bit of C# code
It is time to view a little bit of C# code.
Assuming that you have access to a class definition, there are several
different ways that you can create an object in C#. The most common
way is using syntax similar to that shown in Listing 2 below (note that
the Listing number at the bottom is not part of the code).
Radio myRadio = new Radio();
Listing 2
|
(The code shown in Listing 2, and the following explanation
of that code, would be exactly the same if we were discussing how to write
a similar program using Java.)
What does this mean?
Technically, the expression on the right-hand side of the equal sign
in Listing 2 applies the new operator to a constructor for
the class named
Radio in order to cause the new object to come into
existence and to occupy memory. This simulates the building of the
radio.
(Suffice it at this point to say that a constructor is code
that assists in the creation of an object according to the plans contained
in a class definition. The primary purpose of a constructor is to
provide initial values for the new object, but the constructor is not restricted
to that behavior alone.)
A reference to the object
The right-hand expression in Listing 2 returns a reference to
the new object.
What can you do with a reference?
The reference can later be used to send messages to the new object
(invoke
methods belonging to the new object).
Saving the reference
In order to use the reference later, it is necessary to save it for
later use. (This simulates the installation of the radio in your
car.)
The expression on the left-hand side of the equal sign in Listing 2
declares
a variable of the type Radio named myRadio.
(Because this type of variable will ultimately be used to
store a reference to an object, we often refer to it by the more specific
term reference variable.)
What does this mean?
Declaring a variable causes memory to be set aside for use by
the variable. Values can then be stored in that memory space and
accessed later by calling up the name given to the variable when it is
declared.
Assignment of values
The equal sign in Listing 2 causes the object's reference returned by
the right-hand expression to be assigned to, or saved as a value in, the
reference variable named myRadio (created by the left-hand expression).
Memory allocation
Once the code in Listing 2 has finished execution, two distinct and
different chunks of memory have been allocated and populated.
One (potentially large) chunk of memory has been allocated (by
the right-hand expression) to contain the object itself. This
chunk of memory has been populated according to the plans contained in
the definition of the class named Radio.
The other chunk of memory is a relatively small chunk allocated (by
the left-hand expression) for the reference variable containing the
reference to the object.
Invoking a method on the object
Assume that the definition of the Radio class defines a method
with the following format (also assume that this method is intended
to simulate pressing a frequency-selector button on the front of the radio):
public void playStation(int stationNumber)
What does this mean?
Generally, in our radio-object context, this format implies that the
behavior of the method named playStation will cause the specific
station identified by an integer value passed as stationNumber to
be selected for play.
public and void
The void return type means that the method doesn't return a value.
The public modifier means that the button can be pressed by anyone
in the car who can reach it.
(Car radios don't have frequency-selector buttons corresponding
to the private modifier in C#.)
The method signature
Continuing with our exposure of jargon, some authors would say that
the following constitutes the method signature for the method identified
above:
playStation(int stationNumber)
A little more C# code
Listing 3 shows the code from the earlier listing, expanded to cause
the method named playStation to be invoked.
Radio myRadio = new Radio();
myRadio.playStation(3);
Listing 3
|
(As before, the code shown in Listing 3, and the following
explanation of that code, would be exactly the same if we were discussing
how to write a similar program using Java.)
The first statement in Listing 3 is a repeat of the statement from Listing
2. It is repeated here simply to maintain continuity.
Method invocation syntax
The second (boldface) statement in Listing 3 is new to Listing
3.
This statement shows the syntax used to send a message to a C#
object, or to invoke a method on that object (depending on whether
you prefer OOP-speak or C#-speak).
Join the method name to the reference
The syntax required to invoke a method on a C# object joins the name
of the method to the object's reference, using a period as the joining
operator.
(In this case, the object's reference is stored in the reference
variable named myRadio. However, there are cases where an object's
reference may be created and used in the same expression without storing
it in a reference variable. We often refer to such an object as an
anonymous object.)
Pressing a radio button
Given the previous discussion, the numeric value 3, passed to the method
when it is invoked, simulates the pressing of the third button on the front
of the radio (or the fourth button if you elect to number your buttons
0, 1, 2, 3, 4).