Here is a slightly provocative statement: A software scientist builds a virtual reality --- "an electronic version of real-life" --- in a computer.
For example, a spreadsheet program is a virtual sheet of grid paper that includes a helper "gnome" who adds up the rows and columns each time a human enters a number into the grid. A computerized game (card game, Dungeons and Dragons, etc.) is a virtual reality of terrain and individuals, some of which are controlled by humans, some of which are not. A database program is a virtual filing cabinet whose file folders are linked together with complex virtual strings and clips.
Now, if you want to build a virtual reality inside a computer, you must ask what the entities will be. You design their blueprints, and you activate them --- bring them to life, as many as you want.
Classes are used in object-oriented languages to define the "blueprints"/"genetic codes" and from them we construct objects.
A real-life dog is an entity that has attributes (gender, breed, height, weight, maybe a name) and abilities (can bark, can bite, can eat, can run, can fetch newspaper, can wag tail). A "responsibility" is an activity that an entity must do; a dog might have the responsibility to bark when there is an intruder. Even real-life entities that we consider inanimate have attributes and abilities: A car is inanimate, has lots of attributes, and has the ability to propel a human to Chicago or New York. (A car is unlikely to have responsibilities, however.) When we computerize dogs and cars, their attributes, abilities, and responsibilities will be coded in class definitions as fields, methods, and commands.
Now consider a card game: In a card game, a "card" is an entity with attribute suit (spades, hearts, diamonds, clubs) and attribute count (2, 3, 4, ..., king, ace). A card has few abilities --- maybe it can "tell" you its suit and count, and it has no responsibilities. A "card deck" is an entity that holds cards inside it, can "shuffle itself" and can yield ("deal") cards, one at a time. A deck has no responsibilities.
But a card game has other entities, who do have resposibilities: the dealer (who owns the card deck, who deals from it, and who enforces the rules of the game) and the players (each one owns a "hand" of cards and plays the game). We code these entities as class definitions.
Virtual reality is fascinating because what we consider as inanimate objects in the real world are "alive" in the virtual world, e.g., a playing card. And there are concepts that are not entities in the real world that exist as entities in the virtual world, e.g., a "hand" of cards. We must visualize the virtual world that we build within the computer before we type a line of code. We must make a design before we build.
Consider the card game, Blackjack ("21"), where a dealer gives two cards to each player, and a player collects more cards to obtain a score for her hand that is as close as possible to 21 without exceeding it (else the player loses). (In Blackjack, a numbered card has its number as its value, a face card has 10 as its value, and an Ace can be valued at either 1 or 11, as the player chooses.) The player with the highest score <= 21 is the winner.
Watch this video of a round of Blackjack. What are the entities and their abilities and responsibilities? What are the classes?
How to play blackjack, part 1.
=================================================== // A _NAMESPACE_ IS THE SAME THING AS A VISUAL STUDIO PROJECT: namespace CardConcepts { // AN _ENUMERATION_ IS A NEW "PRIMITIVE DATA TYPE" THAT YOU DEFINE: // enumerations of the Suits and Counts that a playing card can have: public enum Suit { Spades, Hearts, Diamonds, Clubs }; public enum Count { Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King }; // CLASS FOR CONSTRUCTING A PLAYING CARD: public class Card { public readonly Count count; // THIS IS A _FIELD_; SO IS THE LINE BELOW: public readonly Suit suit; // "readonly" means var's value cannot change // THIS IS A _CONSTRUCTOR METHOD_; IT TAKES TWO _PARAMETERS_: public Card(Count a, Suit b) { count = a; suit = b; } // THIS IS A _METHOD_; IT RETURNS AN ANSWER: // returns the int value of a card in Blackjack (it assumes an Ace has value 1) public int BJvalue() { int i = (int)count + 1; // obtain int value that underlies the Count if (i > 10) { i = 10; } // in Blackjack, face cards have value 10 return i; } // returns the string value of a card; overrides default ToString coding public override string ToString() { return count + " of " + suit; } } } ===================================================Make certain you understand all the constructions and all the comments in the above example.
The fields in class Card list its attributes, count and suit; the methods are the abilities. Although inanimate, a card can tell you its Blackjack value and its name (a string).
The class is saved in a Visual Studio project named CardConcepts.
In the main program, we construct objects,
public static void Main() {
Card c1 = new Card(Count.Ace, Suit.Hearts);
Console.WriteLine("card {0} has value {1}", c1.ToString(), c1.BJvalue());
if (c1.suit == Suit.Clubs) {
Console.WriteLine("it's a club!");
}
}
and the card entities come to (virtual, electrical) life.
How do we test and validate the quality of a class that cannot "execute" all alone?
There are special tools to test a class alone (Visual Studio has its own built-in, testing language!), but it is easy to write a test harness for unit testing an individual class. (Like its name suggests, "unit testing" is the testing of one unit of a system.)
A unit test of a class is a script of actions that use one or more objects constructed from the class. The script exercises the attributes and methods. There should be enough unit tests that all the attributes and methods are exercised, in all possible, significant orders.
That last phrase is important --- most classes are defined with multiple methods, where the methods are to be used in a certain order, a protocol.
For example, here is the protocol for using the methods of a text-file object:
=================================================== public enum FileMode { read, write, available }; class TextFile { private string filename; private FileStream address_on_disk; private Mode mode; // should be Mode.Read or Mode.Write public TextFile(string name, Mode mode) { ...code to initialize mode and open the file } public string ReadLine() { ... code to read textline from filename, provided mode == Mode.Read } public string WriteLine() { ... code to write textline to filename, provided mode == Mode.Write } public bool CloseFile() { mode = Mode.available; } } ===================================================The unit tests for class TextFile should include scripts that follow the proper protocol, as well as scripts that call methods in the wrong order (e.g., construct, close, read) as well as scripts that call methods improperly (e.g., construct a read-file and then write to it).
The unit tests should be collected together, used, and reused while the class is coded, and maintained. The unit tests form a "validation suite" for establishing confidence in the quality of the class code.
We write and save unit tests for Project CardConcepts in a new, separate project, call it UnitTests, which we store also within Solution CardConcepts. (See Visual Studio notes to learn how.)
Here is an example of a test harness (the Main procedure of project
UnitTests),
ready to run:
===================================================
using System;
...
using CardConcepts; // ADD THIS LINE and remember to ADD REFERNCE IN VISUAL STUDI)
namespace UnitTests {
class Program {
// executes unit tests of the classes in CardConcepts
public static void Main() {
UnitTest1();
UnitTest2();
... and so on ...
}
// executes a unit test of class Card
static void UnitTest1() {
Card c1 = new Card(Count.Ace, Suit.Hearts);
Console.WriteLine("card {0} has value {1}",
c1.ToString(), c1.BJvalue());
Console.ReadLine("press Enter to proceed");
}
static void UnitTest2() {
... and so on ... }
}
}
===================================================
This code lives in Program.cs within Project UnitTests within Solution
CardConcepts.
To run the unit tests, you must reset the "Startup project" to UnitTests and start the debugger.