Session 1

A Re-Introduction to Object-Oriented Programming


810:062
Computer Science II
Object-Oriented Programming


Writing a Simple Program

Bowling is a popular game in a America. I'm not much of a bowler, but I do know the basic scoring rules. Each player bowls ten frames, consisting of up to two rolls to knock down ten pins. If the player knocks down all ten pins with her first roll (a "strike"), then her score for the frame is 10 plus the number of pins knocked down with the next two rolls. If the player knocks down all ten pins with two balls (a "spare"), then her score for the frame is 10 plus the number of pins knocked down with the next ball. Otherwise, the "open frame" score is the number of pins knocked down with the two rolls.

While conceptually simple, these rules do require a bit of thought. That sounds like a great job for a computer program.

Let's write a Java program to solve this problem:

Create an object which, given a legal series of rolls for a complete game of bowling, produces the correct total score.

I approach such a programming task in this way:

  1. Identify the requirements of the task.
  2. Choose one requirement, a small bit of functionality.
  3. Specify how the program will work when solving this requirement.
  4. Write the simplest code to solve it.
  5. Clean up my program.
  6. If more requirements remain to be done, go to Step 2.

The keys to this approach are small steps, simple code, and cleaning up.

What are the requirements for our bowling game program? It needs to be able to score strikes, spares, and open frames.

Requirement 1: All Gutter Balls

Let's start with a simple requirement. The simpler, the better, as we don't have any idea of what we want for classes or methods yet. The simplest case is the open frame, because it doesn't require any information about future frames. And the simplest case of open frames is all open frames. Even simpler is the case where the player never knocks down a pin.

[ Start with a test... How will I know if my program implements this requirement? ]

    public void testAllGutters()
    {
        BowlingGame game = new BowlingGame();
        for (int i = 0; i < 20; i++)
            game.roll( 0 );
        assertEquals( "all gutter balls gives a score of 0", 0, game.score() );
    }

[ Brief introduction to JUnit, test case classes, my generator... Show above method in context of a TestCase... ]

... This code begins to specify our program. I need a BowlingGame object that responds to roll() and score() messages.

[ Write the code... What is the simplest program that passes this test? ]

    public class BowlingGame
    {
        public BowlingGame()
        {
        }

        public void roll( int roll )
        {
        }

        public int score()
        {
            return 0;
        }
    }

[ Run the test... It passes! But do we worry that it's too simple? No.
We are closer to a solution than we were before, and actually solve one of the cases our program will face. ]

Requirement 2: All Open Frames

Now let's handle the open frames in which the player knocks down pins but never all ten in a frame. Scoring this case is easy enough: sum each successive pair of rolls...

    public void testAllOpenFrames()
    {
        for (int i = 0; i < 10; i++)
        {
            game.roll( 5 );
            game.roll( 4 );
        }
        assertEquals( "ten frames of 9 give a score of 90", 90, game.score() );
    }

[ Write the code... The game needs to remember its rolls. We can use an array. Then score() just loops through the array. ]

[ Run the test... It passes! ]

... Define class, object, method, message, program.


Welcome to the Course

Welcome to 810:062, Computer Science II. This middle course of the department's three-course introductory sequence focuses on object-oriented programming. This course is incredibly cool, because you have a chance to delve much deeper into Java programming and learn how to craft more complex and more flexible programs. Objects will be our tool for doing that.

Take a look at the course syllabus. Like all other course materials, it is on-line at the course web page:

http://www.cs.uni.edu/~wallingf/teaching/062/

[ In class, I briefly introduced some of the information you will find there, including ways to contact me, sources of course material, the textbooks, grading details, and the like. ]


Back to Our Bowling Game Program

Now, let's return to the BowlingGame...

Requirement 3: Player Rolls a Spare

... The same process: write a test, write simple code to pass the test, then clean up.

First the test:

    public void testSpare()
    {
        game.roll( 4 );                 // First frame is a spare.
        game.roll( 6 );
        game.roll( 5 );                 // Second frame is open.
        game.roll( 4 );
        for (int i = 0; i < 8; i++)     // The rest are gutter balls.
        {
            game.roll( 0 );
            game.roll( 0 );
        }
        assertEquals( "A spare counts next ball, too.", 24, game.score() );
    }

Notice how our test helps us to clarify our thinking in code, which we can then use to be sure that our program is correct.

... Run the test, and it fails. But how can we make it work? What is the simplest thing we can do to make it work?

We need the idea of a frame. Let's refactor our existing class to use this idea when scoring the game. [ ... just a temporary variable in the score() method ... ]

... Run the tests again, and the new one still fails -- but the old ones still run. This means that we haven't broken our code! That is good news, and one of the great benefits of writing tests.

Now, what is the simplest thing we can do to make the new test pass? Check to see if the frame score is equal to 10 and, if so, count the next ball, too.

[ Write the code... Run the test... It passes! ]

Notice the process of either writing code to make a new test pass or modifying code to preserve the status quo (but with more flexible code).

... But couldn't we just have planned ahead and used frames in the first place? Yes, but how far ahead? Can I plan *too* far ahead? What's the danger in that?

Requirement 4: Player Rolls a Strike

Start with a test: [ STUDENTS WRITE THIS AS A LITTLE EXERCISE ]

    public void testStrike()
    {
        game.roll( 10 );
        game.roll( 5 );
        game.roll( 4 );
        game.roll( 3 );
        game.roll( 0 );
        for (int i = 0; i < 7; i++)
        {
            game.roll( 0 );
            game.roll( 0 );
        }
        assertEquals( "A strike counts the next two balls.",
                      31, game.score() );
    }

[ ... Run test. It fails. Add if case. Run test. It passes. Hurray! ]

Are we done? maybe, but we can't know for sure. Our tests have isolated specific requirements, and so our code may be limited by our simple approach. We should probably test a few more ordinary or special cases.

... a perfect game.

... a more complex case: alternating strikes and spares.

Both tests pass with no further code, so we can be more confident in our solution. But I'd like to test a more ordinary case...

We have more test code than "real" code. Is that a problem? Not at all! None

... what do we think of the score() method? Its complexity worries me. Complex methods and classes are often signs of an object that we've buried in the code. What is that object in this program? Probably a Frame. (We've talked about frames a lot, but they don't show up as components in our program... That worries me a bit, too.


On Writing and Running Programs

(... incomplete ...)

We can compile and run a program in a couple of different ways. If we use a development environment such as BlueJ, then we may just press a Compile button or a Run button. If we develop our programs from the command line using standard tools such as a text editor and a compiler, then we will enter one or more commands. For example:

    mac os x > emacs BowlingGameTest.java
    mac os x > javac BowlingGameTest.java
    mac os x > java junit.awtui.TestRunner BowlingGameTest
    mac os x > java BowlingGameTest

This semester, we will ask you to step away from an IDE and even from Windows, if that has been your operating system of choice. We'll help you to learn the command line tools on your preferred Unix system (Linux in the labs, but maybe Mac OS X at home). As a computer scientist, you need to know how to work at the level of the operating system, and this is a great way to begin learning.


A Summary of Some High-Level Ideas

One of the founders of object-oriented programming was Kristen Nygaard. He and his colleague Ole Johann Dahl created the first OO language, Simula, in the 1960s. The central theme running through all of Nygaard's work is this syllogism:

The key benefit of OOP follows from this logic, and was the Nygaard's goal in designing a new kind of language: a unified perspective on all phases of software development: analysis, design, and programming.

Computer science studies the information process. Anything is an information process if it can be characterized as having:

OO is a perspective on programming, a way to think about writing programs -- not a paradigm. It is a perspective on the world, a means for organizing knowledge of a domain.


Wrap Up


Eugene Wallingford ..... wallingf@cs.uni.edu ..... January 11, 2005