Sending and Receiving Messages

So far we have had classes that sort of don't interact with each other.

Now we will start to get these classes to combine into a sort of ecosystem of objects that talk to each other.

We will start with a simple example based on the Critter Zoo Program.

We will first create a simple class called Critter which will contain instance attributes name and no_feed (number of times fed), and an instance method feed().

class Critter:
  """ A very simple critter class"""
  def __init__(self, name):
    self.name = name
    self.no_feeds = 0

  def __str__(self):
    desc = ""
    desc += "Instance of Critter\n"
    desc += f"Name: {self.name}\n"
    desc += f"Fed: {self.no_feeds} times\n"
    return desc

  def feed(self):
    print("Yummy! Thanks! Brruppp...")
    self.no_feeds += 1

We will also create a class called Caretaker which represents a person who looks after critters.

class Caretaker:
  """ A very simple critter class"""
  def __init__(self, name):
    self.name = name

  def __str__(self):
    desc = ""
    desc += "Instance of Caretaker\n"
    desc += f"Name: {self.name}\n"
    return desc

  def feed_criter(self, critter):
    critter.feed()

The key method here is feed_critter(), this is now a method that takes in an instance of Critter and then feeds the little thing by calling the instance method feed().

You can think about it like this, feed_critter() takes in a Critter instance and then sends a message to the Critter instance by invoking (calling) its method feed(). The Critter instance in turn receives the message because its method has been invoked, it will then run whatever code is in feed().

Here is the whole program in action to show you how it works.

class Critter:
  """ A very simple critter class"""
  def __init__(self, name):
    self.name = name
    self.no_feeds = 0

  def __str__(self):
    desc = ""
    desc += "Instance of Critter\n"
    desc += f"Name: {self.name}\n"
    desc += f"Fed: {self.no_feeds} times\n"
    return desc

  def feed(self):
    print("Yummy! Thanks! Brruppp...")
    self.no_feeds += 1

class Caretaker:
  """ A very simple critter class"""
  def __init__(self, name):
    self.name = name

  def __str__(self):
    desc = ""
    desc += "Instance of Caretaker\n"
    desc += f"Name: {self.name}\n"
    return desc

  def feed_criter(self, critter):
    critter.feed()

if __name__ == "__main__":
  alan = Critter("Alan")
  ava = Critter("Ava")
  
  print(alan) # print out alan
  print(ava) # print out ava
  
  bob = Caretaker("Bob")
  bob.feed_criter(alan) # feed alan
  bob.feed_criter(alan) # feed alan
  bob.feed_criter(alan) # feed alan
  bob.feed_criter(ava) # feed ava
  
  print()

  print(alan) # print out alan
  print(ava) # print out ava
  

And the output.

Instance of Critter
Name: Alan
Fed: 0 times

Instance of Critter
Name: Ava
Fed: 0 times

Yummy! Thanks! Brruppp...
Yummy! Thanks! Brruppp...
Yummy! Thanks! Brruppp...
Yummy! Thanks! Brruppp...

Instance of Critter
Name: Alan
Fed: 3 times

Instance of Critter
Name: Ava
Fed: 1 times

We can see that we have asked the instance of Caretaker, bob, to do the feeding. We pass in either alan or ava, and bob will feed them by invoking (calling) their feed() method.

=== TASK ===

Create a Plumber class and a Roomba class.

You might want to look at a Goomba...

An instance of Plumber should be able to squash the Roomba.

The Roomba class should have an attribute name and an instance method called squish(). It should also have a non-public attribute _squashed which should be set to False when the instance is created (i.e. in the __init__() method).

Invoking the method squish() should set _squashed to True.

You should also override the __str__() method so that it prints out Hi my name is NAME and I am feeling fine or Hi my name is NAME and I am squashed depending on the value of _squashed.

hetti = Roomba("Hetti")
print(hetti) # Hi my name is Hetti and I am feeling fine
hetti.squish() 
print(hetti) # Hi my name is Hetti and I am squashed

The Plumber class should have a name and an instance method called squash().

The squash() method for the Plumber class should accept a Roomba as a parameter. squash() should then invoke (call) the Roomba's instance method squish().

Here is an example of how the two classes should work.

hetti = Roomba("Hetti")
olga = Roomba("Olga")
bob = Roomba("Bob")
print(hetti) # Hi my name is Hetti and I am feeling fine
print(olga) # Hi my name is Olga and I am feeling fine
print(bob) # Hi my name is Bob and I am feeling fine

merio = Plumber("Merio")
merio.squash(olga) # ask merio to squash olga

print(hetti) # Hi my name is Hetti and I am feeling fine
print(olga) # Hi my name is Olga and I am squashed
print(bob) # Hi my name is Bob and I am feeling fine

Which would output the following to the terminal.

Hi my name is Hetti and I am feeling fine
Hi my name is Olga and I am feeling fine
Hi my name is Bob and I am feeling fine
Hi my name is Hetti and I am feeling fine
Hi my name is Olga and I am squashed
Hi my name is Bob and I am feeling fine

Getting Started

You can get started by copying and pasting the following into main.py.

class Roomba:
  pass

class Plumber:
  pass

def main():
  hetti = Roomba("Hetti")
  olga = Roomba("Olga")
  bob = Roomba("Bob")
  print(hetti) # Hi my name is Hetti and I am feeling fine
  print(olga) # Hi my name is Olga and I am feeling fine
  print(bob) # Hi my name is Bob and I am feeling fine
  
  merio = Plumber("Merio")
  merio.squash(olga)
  
  print(hetti) # Hi my name is Hetti and I am feeling fine
  print(olga) # Hi my name is Olga and I am squashed
  print(bob) # Hi my name is Bob and I am feeling fine

if __name__ == "__main__":
  main()