The answers to most of the questions on the exam could be quite variable and still correct. So this document emphasizes some of the more important points and also attempts to clarify some of the problems that I saw in the answers.
1. For simplicity, assume we have two phones, each with a connector, and directly connected to one another by a pair of wires. Since wires are such simple objects, it is possible to include a wire object directly into a connector, rather than using a pointer to a wire. That is, instead of
class Connector {
public:
static int next_id;
int id;
Wire *in;
Wire *out;
Connector(); };
We could have this, without pointers to Wire objects:
class Connector {
public:
static int next_id;
int id;
Wire in;
Wire out;
Connector(); };
Describe the complications that the second formulation might produce and why the first is a better approach.
1. Answer note: The major problem with the second approach is now the two phones have four "dangling" wires, total. The out wire of one phone has no relation to the in wire of the other, so that placing a packet in the out wire makes no change in the corresponding in wire. This model maps very poorly onto the real model in which a single wire would be connected to two phones. One class of errors in the criticisms was to say that when wires were included within the connectors, that a good deal more memory would be used, as opposed to pointers, the first approach. But we must remember, that in the first approach, the pointers (which take up a bit of memory themselves) actually point to full-fledged wire objects which take up memory in any event. Associated with wires in the first approach are four pointers and two wire objects. In the second approach there are no pointers and four wire objects. Another concern was the destruction of the wire objects -- but the phones, with their connectors are built only once during the entire simulation of the system and disappear trivially when the application terminates. Phones, connectors, and wires are created once. The only really dynamic objects in the system are packets.
2. Write three descriptions of the following line of code from switch.cp:
The three descriptions should be:
2. Answer note:
For 2.1, the array notation selects one pointer to a connector, since the array is an array of pointers to connector objects. Each connector has in it a slot labeled out that is a pointer to a wire. Each wire has in it a slot called packet that is a pointer to a packet. The "->" notation accesses each object via its pointer. The right-hand side returns a pointer to a Packet and the left hand side has the same type. The types of the objects on the two sides of the assignment is not Packet, it is a different type, a pointer to Packet -- they are distinct types.
For 2.2, this statement would be executed when some phone dialed the phone wired to the connector in connectors[i] but only if that phone was hung up so it could receive a ring.
For 2.3, a highly specific answer was not required, just an indication that a switch is an object that contains an array of connector objects. These are accessed only when the switch checks each connection for incoming packets and whenever the switch needs to send a packet to a phone.
3. What does Meyer mean by the statement, "Real systems have no top." In your answer, discuss top-down design and the process of refinement (elaboration). Of course you may want to relate this to the phone system design and function.
3. Answer note: A number of people discussed top-down design and said little about what Meyer meant when he said that "Real systems have no top." Meyer's discussion of top-down design focused on the notion of having a single function that a system was attempting to accomplish. Then the functionality is elaborated by sub-functionalities. He argued that a change in the system specifications could disrupt the design at the top level which could force changes all the way down the line. He also argued that many real systems cannot be well characterized as having a single, well-defined top-level functionality, so the design strategy doesn't match real systems -- they have "no top". In the phone system, the state diagram is a good indication that there's no top. Another is that the phone and switch are designed to work together; neither is "in charge". Top-down design can also lead to overcommitment at an early stage. OO design builds more independent collaborating modules that are generally more tolerant of change.
4. In the PS, no instance of the class Keywords is ever created. How is this class used and how does it contribute to the modularization of the design?
4. Answer note: Both the Phone and Switch classes inherit from the Keywords class we defined. That class has eight static slots, such as
static string *pickup; which are later initialized, in this case to
new string("pickup").
Since the two classes inherit from Keywords, they both "contain" the Keywords slots and their values, which they can use as control strings in packets. In fact, every time a phone or switch instance is created, they include a keyword object, built in to each phone or switch. But Keywords objects takes up no space in either, since they have no ordinary slots, only static slots that exist in only a single place in memory, once for the entire class, not once per instance. The strings act as "globals" for the phone and switch, accessible internally, but existing in only one copy, and referred to by their pointers.
5. If we added pay phones to our system, changes would have to be made in both the phones and the switches. Does this illustrate a design error on our part or not? Explain.
5. Answer note: The key point is that in the real world, when pay phones are added, there have to be changes made to both phones and switches to accommodate them, so it makes sense that this would happen in our model also. Without anticipating in detail just how a pay phone would work, we created a lot of structures that would be easy to extend to a pay phone design. For example, we could add new control strings for packets. The packets have an integer slot that could hold the amount of money deposited. Timers were constructed that could time a call. The state diagram approach could be extended with a few more states. It might be possible to use the existing Booleans or to add at most one more to handle the extra pay phone states. So it appears that the changes that would have to be made are a natural consequence of the extension of the functionality of the system and are not caused by poor design.
6. In the phone and switch, states are manipulated by altering slot values. What differences in the design would arise if each distinct state were a distinct object? Would internal boolean slots in State objects still be useful? Could parameter names such as "FFTFF" be useful? Could a member function of a State object for testing state identity, e.g.,
state_is(State *s);
be designed to use only pointer identity?
6. Answer note: Let us say that we designed a class State, similar to Switch_Phone_State, which is,
class Switch_Phone_State {
public:
bool answered, dialing, picked_up, ringing;
int connected_to; // other phone number
Switch_Phone_State();
bool is_state(bool a, bool d, bool p, bool r);
void set_state(bool a, bool d, bool p, bool r); };
We could create additional static slots in Keywords, of types pointer to State, with names such as,
static State *picked_up;
static State *hung_up; // etc.
We would then create a state for picked_up and set the slot in Keywords equal to (a pointer to) the new State object. From then on, whenever we wanted to check the state of a phone, say, a simple test of the following kind would be all that would be needed,
if(my_state == hung_up) {....}
We would not even need a state_is function. An assignment would have the form,
my_state = picked_up;
This makes the programming very simple. It just tests pointers or passes pointers around. No State objects are ever created or destroyed once the system is initialized. We could retain the Boolean slots and a string inside each state object for convenience and flexibility and debugging, but all computations would be done by simply testing for equality of two states (equality of their pointers) or setting (pointers to) states to named items (also pointers to states), just as we currently do with control strings. There would only be one copy of a State object for each state we actually used, not for all 32 phone states for example. This approach is similar to the use of enums in C and C++, but each of our items has internal structure, which can be convenient. The approach is also quite similar to the use of symbols in Lisp, which would behave in the same way if used as state indicators. We could precisely duplicate the structure suggested above using the Common Lisp object system (CLOS). It would allow us to create instances of a State class and bind each static slot ( a "class" slot) in an inherited Keywords class to a distinct State object.