Books / Ruby for Beginners / Chapter 32

Object State

State is utterly important concept in any object-oriented language. There are other object oriented languages out there: Java, C#, JavaScript. There are also object-orientish languages, which aren’t 100% object-oriented, but share most or some of these concepts (for example, Golang, Objective-C and so on).

The main difference between object oriented language and non-object-oriented is that object oriented languages have a concept of object state. Let’s dive deeper into that.

Back to our example with BMW model E34. Somewhere in Germany, probably in BMW factory in Munchen, there is a drawing of this particular model. Factory produced numerous of instances of this model. However, each model has multiple details:

  • Engine
  • Wind shield
  • Body
  • Doors
  • Wheels, and so on.

All of these objects aren’t useful without being a part of an actual car - why would you need a wheel without having a car? However, all these components being assembled together represent living organism, something that actually works, and this sophisticated object has its own state.

All the BMWs E34 models manufactured in Germany over the course of multiple years were exactly the same. However, all of them have different state. State is what differentiates one car from another. But what does the state look like?

For example, mileage. “Car” object is quite sophisticated mechanism, and it’s unlikely that two different cars have exactly the same mileage. The second parameter that defines a state could be the level of gasoline in the tank. Another parameter that defines a state is the actual state of engine: on or off.

All these parameters can be controlled by car owners. These parameters, including some other parameters that we normally don’t even think of, define the state of an object.

In other words, object in object-oriented language is a living organism, and this organism has its own state. There is a way to change the state of an object: from inside or outside. When we open the door of a car, we’re changing the state from outside. However, we change the state of the engine from the inside. Also, the object itself can change its own state: for example, fan turns on automatically when the engine temperature is too high.

Let’s demonstrate what was written above with a simple proof-of-concept code:

class Car
  def initialize
    @state = :closed
  end

  def open
    @state = :open
  end

  def how_are_you
    puts "My state is #{@state}"
  end
end

car1 = Car.new
car1.how_are_you

car2 = Car.new
car2.open
car2.how_are_you

Result:

My state is closed
My state is open

We’ve created new class “Car” - we’ve made a “drawing” of an object with Ruby language. Then we created an instance of the class by saying “Car.new” and assigned reference to this object to “car1” variable. It’s important to note that “car1” variable doesn’t have the object in it; it’s only the reference to an object located somewhere in computer memory. Remember the analogy with multi-apartment building, where the bell outside leads to specific apartment. The same rule is applicable here: variable is reference to an object. We might have multiple variables pointing to the same object. Below we’re assigning the value of “car1” to “car777”, there are two variables, but only one actual object:

car777 = car1

Then we request a status (state) of this object from the outside by asking “how are you?”, and object reports its state. First car reported that the state is “closed”: “My state is closed”, but why did it happen? Because we defined “initialize” method:

def initialize
  @state = :closed
end

This method gets called every time we create new instance (new object). In other words, every time you say “Car.new”, “initialize” gets called. Nobody knows why we use lengthy “initialize” (you have to spell it with “z”) instead of just “init” or “new” in Ruby. The following looks better, but it’s incorrect, you must use “initialize”:

class Car
  def new
    # ...
  end
end

Car.new

“initialize” method is also referred as “constructor” in documentation and programming books. And in JavaScript version ES6 and above it’s called this way:

class Car {
  constructor() {
    console.log('hello from constructor!');
  }
}

let car1 = new Car();

Run the program above (by typing “node” in your terminal and pasting this snippet) to see “hello from constructor!” message. Method was called during the object initialization. Here is exactly the same code in Ruby language:

class Car
  def initialize
    puts 'hello from constructor!'
  end
end

car1 = Car.new

It’s one of the least obvious things in Ruby: we say “new” and “initialize” gets called.

But why do we need a constructor? Use it to define the initial state of an object. For example, we want doors, windows, trunk to be closed when we produce another car.

You might have noticed that we used “@” (“at” sign) before “state” variable. This syntax is used to define instance variable. We already covered this a little bit in previous chapters. But in general, we have three types of variables:

1) Local variables. These variables are normally defined inside of a method, and not accessible from other methods. For example, the following code will produce the error, because “aaa” variable isn’t defined in method “m2”:

class Foo
  def m1
    aaa = 123
    puts aaa
  end

  def m2
    puts aaa
  end
end

foo = Foo.new
foo.m1 # works fine, prints 123
foo.m2 # won't work, variable not defined

2) Instance variables - variables of particular class instance (or “variables of particular object”). You can access these variables with at-sign “@”:

class Foo
  def initialize
    @aaa = 123
  end

  def m1
    puts @aaa
  end

  def m2
    puts @aaa
  end
end

foo = Foo.new
foo.m1
foo.m2

Instance variables define the state of an object. Nothing prevents you from defining instance variables in class methods, however it’s a good practice to define instance variables in constructor only; you are showing your intention: this is my instance variable, and we’re going to use it.

The following instance variable is defined inside of a method. This code works fine, but result is actually depends on the call order (last two lines):

class Foo
  def m1
    @aaa = 123
    puts @aaa
  end

  def m2
    puts @aaa
  end
end

foo = Foo.new
foo.m1
foo.m2

Result:

123
123

But result is different when two last lines are reversed:

123

Ruby tries to call “m2” method, and instance variable is not set, so default result is “nil”, and “puts nil” produces nothing on a screen. It’s one of the Ruby’s curious features: when instance variable is not defined, it’s assumed to be nil. But this trick won’t work for local variables (without “@” prefix): Ruby will raise runtime error.

3) Class variables. Class variables belong to their class and also known as “static variables”. It’s not the most frequently used feature, and you probably won’t see it too often. Class variables have “@@” prefix, and their value is shared (remains the same) between all objects. When you change this variable, it will be changed for all other objects as well.

There are also two other types of variables in Ruby language:

  • Global variables. They go with the prefix of “$”. You can access them from any point of your Ruby program. Sounds like a good idea, there is a great temptation to use them everywhere, but you should avoid that. It’s not a good practice and excessive use of global variables increases complexity of your program dramatically!

  • Special pre-defined “variables” like “ARGV”, “ENV”, and others. It’s not the actual variables, but Ruby programmers often refer to these pre-defined objects as “variables”. “ARGV”, for example, is used to pass parameters to your program from command-line. “ENV” is used to read your shell environment variables from Ruby program.

Normally, to be able to write an object-oriented program, a beginner should understand the difference between local and instance variables.

And now is tricky question. What does the following code do?

# ...
puts aaa

You say “prints aaa variable to the screen” and you’d be right, because it’s possible to rewrite this program into this one:

aaa = 123
puts aaa

But what if we do it the other way:

def aaa
  rand(1..9)
end

puts aaa

Program above prints the random value (from 1 to 9). In other words, we don’t know for sure what “puts aaa” really means until we understand the context. It’s either variable access or method call (or something else - we’ll talk about “something else” or “method_missing” in the next chapters).

Meanwhile, our class looks like this:

Car class diagram

You can see from the picture above that we have two methods to manipulate the state of this object, they’re highlighted by pink color. Another one, “initialize” is a public constructor - you can only create an object by using this method (you do it with “Car.new”).

State kept in instance variable@state” (naming is up to you, you can name it as you want), and by default there is no any standard way to access this variable from outside. There is no any limitation about how state should look like, it’s all up to you actually. You might want to use ten instance variables and implement sophisticated business logic inside of your class, but API (in our case these two methods, “open” and “how_are_you”) remains the same.

In a similar way to a real car, we might want to play music inside. However, unless it is implemented by our API, nobody will ever notice we have some music playing inside. This principle is known as “encapsulation”.

Suppose you were driving down the road and have met a hitchhiker. Hitchhiker is so shy so she doesn’t want to open the door. She would like to say “how are you”, but before she wants to ensure that the door is open. In other words, we want others to be able to read our state, to read our instance variable(s). How would we do that?

The easiest way is to add a method with a meaningful name that just returns the state. We could add “aaa”, but let’s call it “state”:

class Car
  def initialize
    @state = :closed
  end

  # new method goes below
  def state
    @state
  end

  def open
    @state = :open
  end

  def how_are_you
    puts "My state is #{@state}"
  end
end

Here is the class diagram:

Car class diagram

The state itself, represented by instance variable “@state”, isn’t accessible. However, there is a way to read it through “state” method. State variable and method use the same word “state”, but they are two different things. We could name the method as we want, for example “aaa”, it’s not a mistake. Sounds good, now a hitchhiker can see that the state of a car is “open”, because he or she can read the state with the “state” method.

Well, you can see (read) the state from the outside, but you can only change the state from the inside. Which is, really, a way to go: we might never need the way to close a door from the outside. But what if it is necessary? It’s up to software developers to think about business logic: how are we going to use this or another component? Do we really need this functionality or we can save some time here and avoid implementing that?

If we know for sure we gonna need that, it could be implemented the following way:

def close
  @state = :closed
end

It’s that simple! Here is one of the final class implementations:

Car class diagram

Let’s practice a little bit more and think about what if we need to turn on the engine? Our state could be represented by multiple parameters now (mathematicians would say “defined by the following vector”): open, closed, engine_on, engine_off. We would add two methods: “turn_on”, “turn_off” so we have control over the newly defined state subset. We would’ve end up with quite lengthy class.

Sometimes it’s easier to have “superpowers” and allow any state manipulations from the outside: “I trust you so much, please do whatever you want, open doors, turn the engine on and off, play any music, you’re the master and I’m the puppet”. As you can imagine, this doesn’t always lead to the best outcome. However, this way of manipulating state is widely used. The rule of thumb here is to never cross the line when poor software design bites you back.

Here is how you allow full control over your state or any other instance variable:

attr_reader :state
attr_writer :state

Whoa, looks like magic! It actually is, also known as syntactic sugar. Code above creates two methods: to read and write instance variable:

def state
  @state
end

def state=(value)
  @state = value
end

First method looks familiar, we had exactly the same implementation to read the state. The second method has “=” in its name, so it is used for instance variable assignment.

A curious detail is that these two attr-lines above can be replaced with a single line:

attr_accessor :state

(Don’t confuse “attr_accessor” with “attr_accessible” from Rails framework, these are two different things).

Improved class can be summarized as follows:

class Car
  attr_accessor :state

  def initialize
    @state = :closed
  end

  def how_are_you
    puts "My state is #{@state}"
  end
end

Usage example:

car1 = Car.new
car1.state = :open

car2 = Car.new
car2.state = :broken

car1.how_are_you
car2.how_are_you

Sample output:

My state is open
My state is broken

Visual representation of the class:

Car class diagram

Exercise 1 Implement “Monkey” class with two methods: “run” and “stop”. Each method should change the state of an object. You must expose the state of an object the way it’s accessible (readable), but not writeable from the outside. Create a couple of instances of this class, call newly created methods and ensure your program works.

Exercise 2 Use random state while initializing “Monkey” class, so the monkey either runs or not. Create an array of ten monkeys. Print the state of all the objects to the screen.

Example of Object State

Now it is more or less clear what the state is. But how it is used in practice? What’s the advantage of having a state? Why would we keep the state inside of an object and why do we need to encapsulation?

Well, as you already know, object is a living organism. It turned out that it’s much better to combine under one hood multiple variables that affect the flow of a program, and avoid using them separately.

Imagine we have a robot that walks on the ground, and we’re observing the scene from the top. Robot starts moving at certain point and can move left, right, up and down by any number of steps.

At the first glance it looks like we could avoid using any classes here. We would have two variables: “x” and “y”. We would add “1” to “x” when the robot moves right, “1” to “y” when robot moves up, and so on. There is no need for any objects or classes, and it’s all true. However, there is one caveat. Code complexity grows exponentially when you need to add more robots, and program readability plummets.

With two robots we need four variables, two for each. First tuple is “x1” and “y1”, second is “x2” and “y2”. Not very convenient, but let’s say we can deal with that for now. But what if we have more than two of them? “You can use an array” you say. Yes, it’s true - you can create an array of variables. It will look like a data structure with specific schema, and a set of methods that know how to work with this data structure, how to read this data schema. But hold on a sec, it’s much easier to deal with variables rather than with custom-crafted data structure!

It’s easier to have “x = x + 1” rather than “x[5] = x[5] + 1”. So objects and classes make your program easier to read, understand, and modify. Let’s practice and create a robot class:

class Robot
  attr_accessor :x, :y

  def initialize
    @x = 0
    @y = 0
  end

  def right
    self.x += 1
  end

  def left
    self.x -= 1
  end

  def up
    self.y += 1
  end

  def down
    self.y -= 1
  end
end

robot1 = Robot.new
robot1.up
robot1.up
robot1.up
robot1.right

puts "x = #{robot1.x}, y = #{robot1.y}"

Note the syntax we use to access instance variable: “self.”. You should have “self.” or “@” here, because by default Ruby treats unknown literal as a local variable or method. By using this syntax we’re saying: “it’s instance variable, not local”, “this variable is defined on a class level, not inside of our method”.

Also, try looking into the code above and guess what would this program print. Here is the right answer:

x = 1, y = 3

The robot has made four moves and its coordinates now are 1 by “x”, and 3 by “y”.

We can create ten robots with the following syntax:

arr = Array.new(10) { Robot.new }

And now let’s do the trick and call the random method for every robot in array:

arr.each do |robot|
  m = [:right, :left, :up, :down].sample
  robot.send(m)
end

The trick is two lines inside the each-block. First line picks random Symbol from the array. This symbol is the name of a method we’re going to call: right, left, up, down. The second line is the actually “tricky way” to call the method by a name.

Only with a couple of lines of code we were able not only to create a set of similar objects, but also do some bulk operations on them. Would you agree it was easier than dealing with individual variables one by one?

For the sake of experiment, imagine 60 by 25 scene and put every robot right into the middle. Every second we’ll iterate over array of robots and change their direction the random way, like we did it above. While doing that we’ll be redrawing the map of robots, indicating robot by star, and empty space by dot. We want to see a simple animation of how robots spread out of the middle to the edges.

Below is the code for this program:

class Robot
  # Accessors, so we can access coordinates from the outside
  attr_accessor :x, :y

  # Constructor, accepts hash. If not specified, empty hash will be used.
  # In hash we expect two parameters: initial coordinates of the robot.
  # If not specified, will equal to zero by default.
  def initialize(options={})
    @x = options[:x] || 0
    @y = options[:y] || 0
  end

  def right
    self.x += 1
  end

  def left
    self.x -= 1
  end

  def up
    self.y += 1
  end

  def down
    self.y -= 1
  end
end

# Commander is something that moves a robot.
class Commander
  # Issue a command to move a robot. Accepts robot object
  # and sends it a random command.
  def move(who)
    m = [:right, :left, :up, :down].sample
    who.send(m)
  end
end

# Create commander object, we'll have only one commander
# in this example.
commander = Commander.new

# Array of ten robots.
arr = Array.new(10) { Robot.new }

# Infinite loop (hit ^C to stop the loop)
loop do
  # Tricky way to clear the screen
  puts "\e[H\e[2J"

  # Draw the grid. It starts with -30 to 30 by X,
  # and from 12 to -12 by Y
  (12).downto(-12) do |y|
    (-30).upto(30) do |x|
	  # Check if we have a robot with X and Y coordinates
      found = arr.any? { |robot| robot.x == x && robot.y == y }

      # Draw star if a robot was found. Dot otherwise.
      if found
        print '*'
      else
        print '.'
      end
    end

	# Move to the next line on the screen.
    puts
  end

  # Move each robot randomly.
  arr.each do |robot|
    commander.move(robot)
  end

  # Wait for half a second.
  sleep 0.5
end

Result:

.............................................................
.............................................................
.............................................................
.............................*...............................
.............................................................
...........................*.......*.........................
.............................................................
...........................*.................................
............................*................................
...............................*.*...........................
............................*................................
.............................................................
.............................................................
.............................................................
........................*.......*............................
.............................................................
.............................................................
.............................................................
.............................................................

Demo: https://asciinema.org/a/jMB47AhjBnxgMofSgIVzHObIH

Exercise Let the “initialize” method to accept another option - the number of a robot, so this number is another parameter that defines the state of particular robot instance. Modify “up” and “down” methods, so these methods are no-op (empty, do not do anything) if the number is odd. If the number is even, “right” and “left” methods should be no-op. Try to guess how animation would look like with these changes?


Licenses and Attributions


Speak Your Mind

-->