25. Process Oriented Simulation, take 2

Part of CS:2820 Object Oriented Software Development Notes, Fall 2015
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

Fixing Things

In Java, when we tried to write code like this, it objected:

        public void exit( Vehicle v, float t ){
                /** Simulate v exiting this intersection at time t.
                 */
                System.out.println( "Exiting " + this.toString()
                        + " at time = " + t
                );
    
                Road d = v.pickRoad( outgoing ); 
                if ( queue.isEmpty() ) {
                        occupied = false;
                } else {
                        Vehicle v1 = queue.removeFirst();
                        new v1.ExitIntersectionEvent( t + travelTime );
                }
        } 

We have to rewrite this so that the initializer for ExitIntersecitonEvent is explicitly told about the vehicle v1. So, our call will look like this:

                        new Vehicle.ExitIntersectionEvent( v1, t + travelTime );

This does not end the job. Having made this change, we find that we can't create a new ExitIntersecitonEvent from outside the class because the event notice needed to be defined relative to a particular class instance. This forced us to change the definition of ExitIntersecitonEvent to a static subclass with an explicit field called v, the vehicle that is exiting the intersection. We had to do the same things with ExitRoadEvent. With these changes, the simulation works quite well.

Eliminating non-static methods

The transformation we just performed above is a special case of a general transformation that can be applied to any object oriented program. Consider this little (nonsense) code fragment:

class C {
        private int f; // some field of the object
        public void m( int g ) { // method m
                f = g;
        }
}

// somewhere else in the program
        C o;    // o is an object of class C
        o.m(5); // apply the method C.m to o

We can always rewrite this code as:

class C {
        private int f; // some field of the object
        public static void m( C o, int g ) { // method m
                o.f = g;
        }
}

// somewhere else in the program
        C o;      // o is an object of class C
        C.m(o, 5) // apply the method C.m to o

This transformation always works. In fact, this is the central thing that the original preprocessor for C++ did when it converted object-oriented C++ code to non-object oriented C code.

Note that if all methods are static, you can no-longer claim to be writing object-oriented code. Your classes, such as they are, are merely data organizers, with related subroutines (that do not return values) or functions (that do return values). Static methods with names like C.m() above are really just global subroutines that could have names like C_m() if Java allowd them (note that C++ does allow exactly this, if you want to ignore the object-oriented features of C++ and program in it as if it was plain C.

No method needs more than one parameter

There are a number of other transformations that we can apply. For example, we never really need multiple parameters for any method. Consider this little (nonsense) code fragment:

class C {
        private int f; // some field of the object
        public void m( int g, int h ) { // method m
                f = g + h;
        }
}

// somewhere else in the program
        C o;       // o is an object of class C
        o.m(5, 6); // apply the method C.m to o

We can always rewrite this code as:

class C {
        private int f; // some field of the object
        public int g;  // a parameter to method m
        public void m( int h ) { // method m
                f = g + h;
        }
}

// somewhere else in the program
        C o;       // o is an object of class C
	o.g = 5;
        o.m(6);    // apply the method C.m to o

We have made no changes to how the program does its job, we have just changed how parameters are passed to the method m. We can even make the field g private if we add a new method to set g:

class C {
        private int f; // some field of the object
        private int g; // a parameter to method m
	public void passG( int newg ) { // parameter passing to method m
		g = newg;
	}
        public void m( int h ) { // method m
                f = g + h;
        }
}

// somewhere else in the program
        C o;       // o is an object of class C
	o.passG = 5;
        o.m(6);    // apply the method C.m to o

This may seem stupid, but it has a real role. In Intro to Psych, when I took it years ago, one of the things I learned about is the magic number 7 plus or minus 2. Numerous experiments have shown that people's short term memories have a capacity of about 7 items. Psychologists call them chunks because the size of an item is ill-defined. If you read people a sequence of random digits such as 3710583, and then hand them a pencil and ask them to write the sequence down, most people will be able to do that for sequences of about 7 digits. On the other hand, if you read an Iowa City resident a number such as 3193350740, 10 digits long, many will be able to remember it, particularly if you pause in the right places, reading it as 319 335 0740. That is because 319 is just one chunk, the area code of Iowa City, and 335 is another chunk, the standard prefix on all University of Iowa academic phone numbers.

How does this apply to programming? Consider this method call:

	a.b(c,d,e,f,g);

When trying to deal with this call, there are about 7 chunks in it. For methods with fewer parameters, you can most likely juggle them in your head, but for methods with more parameters, you most likely need to write things down, keeping a manual page in one window while you struggle to get things right in another. In this context, it starts making good sense to transform the code to something like this:

	a.setup(c,d);
	a.b(e,f,g);

This makes the most sense when the setup code sets options and values that will remain the same through multiple calls, while the second call has parameters that tend to be different each time they are used. For example, a typical text display subsystem on a graphics computer screen has a logical operation something like the following:

	window.putchar( x, y, font, color, angle, char );

Calling this operation for each character would be extremely cumbersome, and in any case, we usually put out a number of characters all with the same attributes, so we rework the interface to look like this:

	window.settextattributes( font, color, angle );
	window.putchar( x, y, char );

And then, we make an additional change, having the window also retain the current screen coordinates and update them by the width of each character as it is displayed, so we end up with this:

	window.settextattributes( font, color, angle );
	window.setat( x, y );
	window.putchar( char );

Now, we can set the attributes for a whole block of text, and then set the starting location of a line, output all of the characters in that line, and then set the starting location of the next line. We made no change to the underlying algorithm for displaying a character, all we did is change the architecture of the interface to that algorithm.

Software architecture

The architecture of a piece of software describes how a user interacts with it, just as the architecture of a building describes how a user interacts with the building.

From an architectural standpoint, you do not care very much about what algorithms are used, except to the extent that they have an impact on things users care about like price and performance.

When speaking of a package or a class, where the users are the programmers making use of that package or class in their programs, the architecture is primarily exposed in the public interface to that package or class. This architecture can be changed in many ways while making no changes to the underlying algorithms.

In this semester, up to this point, we have explored several different architectures for the class Simulator that change the way programmers write discrete event simulation programs. We used lambda expressions and explicit subclasses to achieve the same behavior.

In designing small programs, architecture hardly matters. In big programs, poorly selected architecture can have a significant impact.

The Code

Eample code incorporating the changes discussed in this lecture is available here. This is a modified version of the code distributed with Lecture 17 and 21. Largely, this is finishing the transformation of the simulation methodology so that events in the vehicle process do all the work, but this code also demonstrates the elimination of multiple parameters in that road.enter(v,t) has been changed (rather pointlessly) to road.setV(v);road.enter(t).