We have updated the content of our program. To access the current Software Engineering curriculum visit curriculum.turing.edu.
Object-Oriented Programming
Learning Goals
- Understand the distinction between a variable and what it references
- Understand how objects can interact
- Understand the process Ruby use to look up an instance method
- Understand self
Vocabulary
- Variable
- Object
- Reference
- Pointer
- Self
Object Oriented Programming
Variables and Objects
There is an important distinction between variables and what that variable holds. Take a look at this example:
class Dog
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end
dog_variable = Dog.new("Fido")
other_dog_variable = dog_variable
Turn and Talk: How many Dog objects are there in this example?
There is only one Dog object in this example. Ruby creates an Object when it does .new. The line other_dog_variable = dog_variable DOES NOT create another Dog object, it creates another variable.
Variables are not Objects themselves. They merely reference the Object. You will also hear this as, variables point to objects. We can diagram this like so:
dog_variable--->    #<Dog:0xA007F...>    <---other_dog_variable
In this case, two variables reference the same object. We can demonstrate this like so:
class Dog
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end
dog_variable = Dog.new("Fido")
other_dog_variable = dog_variable
dog_variable.name = "Lassie"
puts other_dog_variable.name
Turn and Talk: What will be printed to the screen?
Let’s look at another example
class Owner
  attr_reader :pet
  def initialize(pet)
    @pet = pet
  end
end
class Dog
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end
dog = Dog.new("Fido")
owner = Owner.new(dog)
Turn and Talk: How many Dog objects are there in this example? For each Dog object, which variables reference that object?
Bonus: What is the scope of those variables?
Method Look Up
We can think of methods as messages. Just like messages they have receivers. When you type the code some_object.method_name, you are sending the method_name message to the some_object object. Another way to say this is that methods run on objects. When the Object receives that message, it looks in its Class to find that method.
Let’s look at this example:
class Owner
  attr_reader :name,
              :pet
  def initialize(name, pet)
    @name = name
    @pet = pet
  end
end
class Dog
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end
dog = Dog.new("Fido")
owner = Owner.new("Jeff", dog)
puts owner.name
Both these Objects have a method called name, so if we call .name, how does Ruby know which one to call? The key here is that methods run on Objects. In this example, we are calling .name on an Owner object (remember, the variable owner is storing an Owner object), so Ruby will execute the name method in the Owner Class.
self
Let’s add to this example:
class Owner
  attr_reader :name,
              :pet
  def initialize(name, pet)
    @name = name
    @pet = pet
  end
  def introduction
    introduce_self + " and " + introduce_pet
  end
  def introduce_self
    "Hello, my name is #{@name}"
  end
  def introduce_pet
    "My pet's name is #{@pet.name}"
  end
end
class Dog
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end
dog = Dog.new("Fido")
owner = Owner.new("Jeff", dog)
puts owner.introduction
Turn and Talk: With your partner, walk through this code and explain to each other what it is doing.
The introduction method calls two other methods: introduce_self and introduce_pet. We just said that methods run on objects, so what objects are these methods running on? The answer is self. Whenever we call methods without the dot notation, there is an implied receiver of self. Change the example to use self:
class Owner
  attr_reader :name,
              :pet
  def initialize(name, pet)
    @name = name
    @pet = pet
  end
  def introduction
    self.introduce_self + " and " + self.introduce_pet
  end
  def introduce_self
    "Hello, my name is #{@name}"
  end
  def introduce_pet
    "My pet's name is #{@pet.name}"
  end
end
class Dog
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end
dog = Dog.new("Fido")
owner = Owner.new("Jeff", dog)
puts owner.introduction
If we run this code, it will still work exactly the same. self means “the current object”, so when Ruby sees self inside the introduction method, it is referring to the Owner object that we called introduction on, i.e. the Owner named “Jeff”. Ruby then knows to look in the Owner class for the introduce_self and introduce_pet methods.
You’ve run across the error undefined local variable or method. Now we can see how Ruby can treat something as a local variable or a method. In this case, it first looks for a local variable named introduce_self and introduce_pet. When it can’t find them, it then looks for a method. We can illustrate this by changing the example to:
class Owner
  attr_reader :name,
              :pet
  def initialize(name, pet)
    @name = name
    @pet = pet
  end
  def introduction
    introduce_self = "Hello"
    introduce_pet = "goodbye."
    introduce_self + " and " + introduce_pet
  end
  def introduce_self
    "Hello, my name is #{@name}"
  end
  def introduce_pet
    "My pet's name is #{@pet.name}"
  end
end
class Dog
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end
dog = Dog.new("Fido")
owner = Owner.new("Jeff", dog)
puts owner.introduction
Practice: With your partner, look at the following code example.
- For each binding.pry, try to predict what the value ofselfwill be on that line of code. Check your answers by running the code and typingselfinto the pry session:
- For each puts, try to predict what will be output to the screen.
require 'pry'
class Owner
  attr_reader :name,
              :pet
  def initialize(name, pet)
    @name = name
    @pet = pet
    binding.pry
  end
  def introduction
    self.introduce_self + " and " + self.introduce_pet
    binding.pry
  end
  def introduce_self
    "Hello, my name is #{@name}"
  end
  def introduce_pet
    "My pet's name is #{@pet.name}"
  end
end
class Dog
  attr_accessor :name
  def initialize(name)
    @name = name
    binding.pry
  end
end
binding.pry
dog = Dog.new("Fido")
owner_1 = Owner.new("Dan", dog)
owner_2 = Owner.new("Terry", dog)
puts owner_2.introduction
dog.name = "Lassie"
puts owner_1.introduction
puts "owner_1 pet name is #{owner_2.pet.name}"