Object Oriented Programming/State?

From Wikibooks, open books for an open world
Jump to navigation Jump to search

State Machines[edit | edit source]

If you are unfamiliar with state machines, we won't delve too deep into them, but we highly recommend you look into them more. It's good for your clarity of thought process. Pedantically speaking, state machines are somewhat complicated mathematical constructs, and there are many interesting results that can be derived using the mathematical notation. But that's not our interest. Our interest is two-fold:

  • For one, state machines provide a clear graphical way to illustrate potentially complicated application logic. First, you identify what your possible states are. For example, a server might be IDLE, CONNECTED, BUSY, ERROR, etc. Then, you define all the possible inputs, i.e. messages that the client might send, and for each state diagram the server's response to each input. This helps promote thoroughness as you think about reactions to unanticipated messages in various states.
  • Secondly, state machines are a standard tool with a relatively standard notation. There are tools and libraries that will automatically make code given some canonical state machine format as input. This is effectively what regular expression processors do.

In object oriented programming, "objects" are sometimes implementations of state machines. State machines are a better conceptual underpinning for behavioral entities —servers, regular expression processors, graphics engine, and virtual machines are all good examples of behavioral entities.

A basic distinction between simple objects and behavioral entities is that simple objects do not change their behavior based on their value. In fact, we tend to refer to their value rather than state. Simple objects tend to be kept in collections and searched through, passed to and returned from functions, and in general act like values. String and Rectangle are classic examples. Later on, we'll discuss message passing as an abstraction of method invocation, and it may seem a little strange in the context of simple objects.

If you are still having trouble with the concept of objects, download and play with BlueJ, Most people "get it" within hours of using that IDE.

"State" is Evil![edit | edit source]

Let's look at an example, now in Ruby:

We're displaying something over time, so we're dealing with both pixels and seconds. Based on the zoom level, there is a correlation between them: pixels per second, or PPS.

class Timeline
 {
  PPS;
  current_position_in_seconds;
  current_position_in_pixels;
 public:

  GetPosInSeconds() {current_position_in_seconds;}
  SetPosInSeconds(s) {current_position_in_seconds = s;} # oops, we're out of sync
  GetPosInPixels() {current_position_in_pixels;}
  SetPosInPixels(p) {current_position_in_pixels = p;} # oops, we're out of sync
}

In this example, we're maintaining gratuitous state — which is to say, we're holding the position in both seconds AND pixels. This is convenient, and important for users of this class, but we've messed up the encapsulation here. Whenever you set the value in one unit, you've destroyed the correctness for the other unit. Okay, you say, here's a simple fix:

class Timeline
{
  PPS;
  current_position_in_seconds;
  current_position_in_pixels;
public:

  GetPosInSeconds() {current_position_in_seconds;}
  SetPosInSeconds(s)
       {
          current_position_in_seconds = s;
          current_position_in_pixels = s*PPS;
        }

  GetPosInPixels() {current_position_in_pixels;}
  SetPosInPixels(p)
       {
          current_position_in_pixels = p;
          current_position_in_seconds = p/PPS;
       }
}

This fixes the obvious error with the previous example, but you've created a lot of work for yourself. Now you have to keep every calculation in every method in the Timeline class duplicated to maintain consistency. There could be dozens more where this came from (trust me). And what about when you add in other units, say inches? You have to duplicate all of that again. Presumably, you see where this is heading: calculated properties, logical properties, virtual properties, whatever you like to call them. Let's see this example again:

class Timeline
{
  PPS; # Pixels Per Second
  IPS; # Inches Per Second
  current_position_in_seconds;
public:

  GetPosInSeconds() {current_position_in_seconds;}
  SetPosInSeconds(s) {current_position_in_seconds = s;}
 
  GetPosInPixels() {current_position_in_seconds*PPS;}
  SetPosInPixels(p) {current_position_in_seconds = p/PPS; }
 
  GetPosInInches() {current_position_in_seconds*IPS;}
  SetPosInInches(i) {current_position_in_seconds = i/IPS; }
}

Now, we're presuming that the conversion factors of PPS and IPS are set correctly for simplicity, but otherwise we've vastly simplified our problem. In twenty other functions we now write, we only have to worry about seconds, there's no consistency problems to worry about. Additionally, if we need to change our base units from seconds to pixels, the Timeline users need never know.