SLIP INTO RUBY PART 1 - OBJECT-ORIENTED WHA?

Part 1 of a series explaining Ruby and RGSS in a way you can understand.

  • Trihan
  • 07/18/2012 11:40 PM
  • 19836 views
Hello folks, and welcome to part 1 of...



My goal is to offer a resource to budding RMXP/VX/VX Ace users who know that there's a scripting language there but don't have the first idea what it is or how to use it. Or people who DO have a first idea but aren't quite getting it.

However, before we can really get into the meat of what RGSS is and can do, it makes more sense to take a look at it from a wider viewpoint. So we're going to start with a bit of a primer on what, exactly, object-oriented programming is.

I could talk about classes and methods and properties and instance variables and inheritance and parameters and arguments until I'm blue in the face, but I know there are some people out there who wouldn't have a clue what I was on about. So we're going to make a cat instead.

If you're starting with a blank slate, you can't ask Ruby for a cat, because it has no idea what one is. Before we can make one, we need to provide a template from which all cats will be manufactured; a blueprint for a cat, if you will. In Ruby, this template is called a “class”:

class Cat
end


This is one of the simplest classes you can write. It's the minimum amount of code required to be able to say “Hey Ruby, give me a cat please.” If at some point we find ourselves in need of a cat, it's simple: all we have to do is create a new instance of Cat with a unique name, like so:

tiddles = Cat.new


We now have a cat, but it isn't a very exciting one because it just sort of sits there and doesn't do much. However, there is one thing it did that you couldn't see; it called its constructor method (“method” being Ruby's term for a subroutine or function)

The constructor method is a specially-named one which is always called “initialize” and is always called as soon as an instance of the class is created; if you don't provide one explicitly, there's a built-in one for all classes which Ruby will call for you, but this isn't always ideal if we want to do anything specific when we make a new cat. We'll come back to this later, so don't worry too much about it right now.

The main concern we have at the moment is that the cat can't breathe, which means it isn't going to live very long unless we give it some way to do so. Let's do that quickly before our pet expires:

class Cat
	def breathe
		#insert code for inhaling oxygen and exhaling CO2
	end
end


There we go. Now any instance of a cat is able to breathe by calling this method when it's needed in the program. You call a method by entering the name of the instance, followed by a period, followed by the method name, like so:

tiddles.breathe


And our moggy lives to see another day. However, as you can see it's still not the most exciting of pets...maybe we'd have more luck with a dog?

class Dog
end

rover = Dog.new

rover.breathe


Uh oh. Rover's just informed us that he can't find a method called “breathe”, and he's rapidly turning blue. What went wrong?

Well a dog isn't a cat, so dogs can't really do anything that cats can. If we want Rover to breathe, we need to add the method to his class too:

class Dog
	def breathe
		#code for breathing
	end
end


There we go, much better. But Rover's just as boring as Tiddles. And the fact that we've replicated the exact same code bothers me. If only there were some way our cat and dog could share the breathing method between them...

Here's where class inheritance comes in. See, classes can be children of other classes, and the great thing is they will inherit all of the properties and methods of their parent. What do cats and dogs have in common? They're animals. What do all animals do? Breathe. So rather than replicating the breathe method for every animal in the world, it'll be much easier to let animals in general do the breathing and then pass it on to cats and dogs via inheritance. And we do that like so:

class Animal
	def breathe
		#code for breathing
	end
end

class Cat < Animal
end

class Dog < Animal
end


Excellent! Now we can do

tiddles.breathe
rover.breathe


And our pets have no respiratory problems whatsoever. We could even give ourselves a goldfish and as long as we specified that he's an animal too he can breathe like the rest. Of course, fish don't breathe quite the same way as cats and dogs, but that's a topic for later.

I feel quite sorry for poor Tiddles and Rover, so my next task is to give them a way to communicate with me. Let's see...

class Animal
	def breathe
		#code for breathing
	end
	
	def meow
		print Meow!
	end

	def bark
		print Woof!
	end
end


So now we can do:

tiddles.meow
rover.bark


And our output on the screen would be:

Meow!
Woof!


But wait. Inheritance isn't always a good thing...

tiddles.bark
rover.meow


Yeah, sometimes animals just need to have their own sounds. Let's fix this:

class Animal
	def breathe
		#code for breathing
	end
end

class Cat < Animal
	def meow
		print Meow!
	end
end

class Dog < Animal
	def bark
		print Woof!
	end
end


There we go, now dogs can only bark and cats can only meow. Our pets are able to communicate and breathe. They're starting to look much better already!

So tell me, how old is Tiddles? What? You don't know? Why not?

Well we haven't even told Ruby that our furry friends have ages. In fact it doesn't even know they're furry because we haven't told them about fur either. Things like this are called properties, and determine the kinds of data that can be held about the class.

So that we can answer the above question, let's give animals an age property. All animals have an age, so we might as well make it common to any animals we need. There are several ways to do this; I'll show you the least abusable way, then explain the alternative and tell you why it's a bad idea.

class Animal
	def initialize
		@age = 0
	end

	def breathe
		#code for breathing
	end

	def age
		@age
	end

	def age=(val)
		@age = val
	end
end


Oh man, that's a bit more complicated than we're used to so far, but don't worry, it's pretty simple. The “@” is basically telling Ruby that this variable is an “instance variable”--that is to say that every new instance of the class will have its own version. This means that Tiddles the cat will have an age independent from that of Felix the cat. If we didn't denote it as an instance variable then all cats would share an age, and that would just be chaos.

The new methods are there so that we can find out the age of a cat, or change its age if needed. So now when I ask you “How old is Tiddles?” you need only enter this in your program:

tiddles.age


And the returned value will be the age of Tiddles. You can also do

tiddles.age=5


And Tiddles will magically age to 5 years old.

This is, in my opinion, the best way to do this. What you could have done was this:

class Animal
attr_accessor	:age
	def initialize
		@age = 0
	end

	def breathe
		#code for breathing
	end
end


“attr_accessor”, “attr_reader” and “attr_writer” are essentially a shorthand way of doing the get and set methods we created in the previous example, but there's one important difference. attr_accessor basically means the variable can be read or written to at any time by anything anywhere. This might result in you accidentally changing a cat's age in a part of your program that has nothing to do with cats, and if you don't realise you've done it that's going to be a bitch to find and debug later.

The way we did it was by creating what are known as “wrapper” methods, which allow you to read and write instance variables but only from within their own class, limiting the circumstances under which one might change a variable's value without meaning to.

So now we have a parent class, Animal, which determines the things an animal is able to do. We might end up adding other methods to this like jump, swim, mate and so on. Then for the cat class, we might add catch_mice, scratch_human, land_gracefully...you get the idea.

You may have caught on to something I mentioned earlier when I said that some animals might not breathe the same way as others. Fish certainly don't breathe the same way cats do, but currently if you made fish an animal the program wouldn't know that. We'd have to do what's called “overloading” and give fish their own, unique way to breathe. We simply do that by redefining the breathe method in the fish class:

class Fish < Animal
	def breathe
		#insert custom code that only applies to fish
	end
end


Now although fish inherit methods from Animal, we've told the program that they breathe differently from other types. Basically a method's functionality will be determined by the lowest level at which it's defined: methods in a specific class will take precedence over the parent, which will take precedence over the containing module (we'll cover modules later, don't worry).

And that's really all you need to know to be able to start recreating the world in Ruby! These concepts are the only things necessary to model the everyday things you see around you--and, indeed, kickass RPG systems. That's why you're here, right?

Recap:

  • Classes are the “templates” used to create an object.

  • Methods are the functions/subroutines that determine what the class can do.

  • Properties are pieces of data that describe the class.

  • Classes can be children of other classes, and will inherit any properties or methods the parent has in addition to its own.

  • Properties can be read/written either by creating wrapper methods, or designating them as one of “attr_accessor”, “attr_reader” or “attr_writer”. Note that attr_accessor will allow reading and writing, attr_reader will be read-only and attr_writer will be write-only.

  • If a module, class or subclass have methods with the same name, the one that's lowest in the hierarchy will be used. Redefining a method that was already defined further up the chain is called “overloading”.


That's it for part 1 of Slip into Ruby! Feel free to tell me what you think in the comments; please let me know if I've said anything that's inaccurate or I missed something out in my explanations that would be useful.

In part 2 I'm intending to cover properties in more detail, including the different kinds of variable, and possibly go into parameters and arguments. If there's anything else you'd like an explanation on please let me know and I'll cover it either in part 2 or a later one.

Until next time!

Posts

Pages: first 12 next last
That was very informative and interesting to read. This was one of the few tutorials that I actually read all the way through. Bravo.

I've been trying to wrap my head around Ruby and an explanation set out like this helped unravel some uncertainties I had thus far. Thanks for this!
This is a really nice guide. I've been trying to get myself to learn Ruby for a while now, and this might be just what I need.

However, speaking as another Object Oriented Programmer, I think you could've went over methods a bit more. Like talk about how every object has behaviors which we call methods. These methods illustrate certain actions. In this case, animals breathe. Or something because I know after tutoring this stuff that the concept of classes and methods is one of the hardest things to understand apparently.

Also, you might want to get into algorithms and specific procedures a little if you really want to talk about object oriented, or any sort of imperative, programming. You kind of comment over stuff but being specific is so vital to coding....
Trihan
"It's more like a big ball of wibbly wobbly...timey wimey...stuff."
3119
They're good points, but I'm trying to explain it in more of an everyman sort of way without getting bogged down in theory and complex explanations. That's the sort of thing that people who aren't that savvy about programming aren't going to benefit from knowing.

Or to put it another way, I'm trying to teach the how more than the why. I can go into detailed explanations later once I've covered the basics.
Well you don't really have to define what a method is and how it functions and stack calls and whatnot lol. I guess the methods part is fine but in terms of procedures, well... Skip over it now and just write basic code, and later it'll bite you in the ass you know? Of course, you could write 3 articles on algorithms so.... I guess it's your guide haha
Trihan
"It's more like a big ball of wibbly wobbly...timey wimey...stuff."
3119
I figure if people want to dig really deep into algorithms and programming theory there will be articles elsewhere that teach it. I'm really just here to get people started with using RGSS to customise their projects.
Adon237
if i had an allowance, i would give it to rmn
1743
I love you.
Trihan
"It's more like a big ball of wibbly wobbly...timey wimey...stuff."
3119
The marriage certificate's in the post. Let's make this happen!
You should use our code ruby tags for syntax highlighting!
class Fish < Animal
	def breathe
		self.die if self.water.oxygen_ppm < 10
	end
end
Trihan
"It's more like a big ball of wibbly wobbly...timey wimey...stuff."
3119
I thought I had done that properly; what do I need to change?
Sailerius
did someone say angels
3304
A couple points of feedback:
Tiddles = Cat.new

I would make it tiddles instead of Tiddles, as variables whose name starts with a capital letter are constants in Ruby. It's above the level that this tutorial covers, but you don't want to start newbies on bad practices.

def set_age(new_age)

@age = new_age

end

Although technically correct, this is a C++/Java idiom and isn't the preferred Ruby style. The Ruby idiom for getters and setters is:

def age
   @age
end

 def age=(val)
   @age = val
 end
Trihan
"It's more like a big ball of wibbly wobbly...timey wimey...stuff."
3119
author=Trihan
I thought I had done that properly; what do I need to change?


I know you figured it out but for anybody else the code ruby tags are case sensitive: Ruby will just do a regular code tag without syntax hightlighting. I forgot to say that in my original post :(
Great start and an informative tutorial, thank you for writing this.
Gibmaker
I hate RPG Maker because of what it has done to me
9137
One thing I would disagree with is not using attr_accessor! Unless there's a special method that has to be run specifically when accessing a property (such as logging how many times it's accessed?) there doesn't seem to be any particular advantage. The small methods you wrote as examples would work exactly the same as setting it as attr_accessor, in that the property can still be "read or written to at any time by anything anywhere"! If you mean that you only want @age to be accessible from within the class, then you simply don't create any special designations for it, and it will be invisible from outside its own class.
Trihan
"It's more like a big ball of wibbly wobbly...timey wimey...stuff."
3119
I used to think the same thing, but someone with more experience in Ruby than me said it was safer practice to use wrapper methods. That said, it would only have made a difference when I had the original names for the methods before GRS recommended changing the syntax. As it is now, I don't think there's any functional difference, so you're right.
I just recommended changing the code tags for syntax highlighting, I think you're thinking of Sailerius' suggestion.
Trihan
"It's more like a big ball of wibbly wobbly...timey wimey...stuff."
3119
Right again, sorry. :P
Gibmaker
I hate RPG Maker because of what it has done to me
9137
author=Trihan
I used to think the same thing, but someone with more experience in Ruby than me said it was safer practice to use wrapper methods. That said, it would only have made a difference when I had the original names for the methods before GRS recommended changing the syntax. As it is now, I don't think there's any functional difference, so you're right.


That's true, it would protect against errors like mixing up = and ==. But I feel the uncluttered syntax makes up for the extra caution you'd need when working with it.
kentona
I am tired of Earth. These people. I am tired of being caught in the tangle of their lives.
21217
when is part 2 coming out?
Pages: first 12 next last