When is a class called for

I’m new to Ruby and to OOP and unclear when declaring a class is called for. I wrote a small text based menu routine in Ruby and wrapped it in a class but fail too see any advantage to having a class wrapped around a simple stand alone method. Rather the opposite since it means more typing.
Now I’ve written a Ruby version of dir_walk() from High_Order_Perl by MJ Dominus and wondering if there is any advantage in putting a class wrapper around it.
Comments welcomed.
Be well,
Mike

You generally use a class when you have some kind of state to combine with some methods which change or retrieve that state.

For example, you want to represent various people, store their names and telephone numbers, and provide methods to access that information:

class Person
  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
    @telephone_number = ""
  end

  def get_fullname()
    return @first_name + " " + @last_name
  end

  def set_telephone_number(number)
    @telephone_number = number
  end

  def get_telephone_number()
    return @telephone_number
  end
end

p1 = Person.new("John", "Smith")
p2 = Person.new("Sally", "Anne")

puts p1.get_fullname()
p2.set_telephone_number("111222333")
print p2.get_fullname(), ": ", p2.get_telephone_number()
puts

If your program is more than one file, then sometimes you will collect common methods in a Module: they can then be called with the Module name as an extra qualification, e.g.:

module ExampleNamespace
  def self.say_hi()                        # notice the 'self'
    puts "Hi from module"
  end
end

ExampleNamespace::say_hi()

In OOP, the concept of class is that you have an Object, and that has some data in it. If that’s sounds confusing don’t worry.

Say you saw a snake:

  1. The snake can go up, down, right, left etc.
    These are the verbs, it is what a snake can perform. For the example here, ignore eat, sleep etc.

  2. And the snake has some characteristics like colour, length, etc. which represents the snake.

Now map the above with this:

  1. In Object Oriented Programming, go_up(), go_down(), go_left(), and go_right() will be the method of Snake object.

  2. In OOP, the characteristics of snake are variables. Especially instance variables.


So in Ruby, the snake class will be represented as:

#!/usr/bin/ruby -w

class Snake
	@@snakes = 0

	def initialize(colour, length)
		@colour = colour
		@length = length

		# Snakes born in the middle of the land
		@position = [0, 0]
		# [0, 0] => [x, y] position of the snake in the vast land.

		@@snakes += 1
	end

	def go_up()
		@position[1] += 1
	end

	def go_down()
		@position[1] -= 1
	end

	def go_left()
		@position[0] -= 1
	end

	def go_right()
		@position[0] += 1
	end

	def position
		"The snake is now at #{@position[0]} at X and #{@position[1]} at Y"
	end

	def self.total_snakes
		@@snakes
	end
end

snake = Snake.new('black and blue', '2 meters')
snake.go_up

snake.go_right
snake.go_right

snake.go_left

snake.go_down
snake.go_down
snake.go_down

p snake.position    # => "The snake is now at 1 at X and -2 at Y"
snake2 = Snake.new('red and black', '1 meter')
p Snake.total_snakes    # => 2

If you can draw it in a graph, the first snake will be here:
image

The snake2 however, will be at 0, 0. You can call go_up() 5 times:

5.times { snake2.go_up() }

And the snake2 will be at 0, 5. But that won’t affect the position of the first snake we created.

The weird looking @ (single) sign (aka at sigil) specifies that a variable is belonging to the object.
See the example again, it actually stores information about the snake. The @position variable is actually used here for the example. It is actually called an instance variable.

You see, we track the total_snakes with “@@snakes variable”, it’s actually a class variable. It’s represented with @@ sign (which is double at sigil). Here in the example, the class variable tracks how many snakes were created in total. It’s not something you want to call on a snake, but on the Snake class. Your code may differ.


Fun fact: I have written a real snake game with Ruby2D:

This game also operates like the example, but on a real 2D scene. That’s not too hard to do in Ruby. Take a look at the code, probably you will be mesmerized because the code is written poorly to make it fit in least lines (150 lines).


So probably you got the basic concept of OOP. Instead of snake, you can do the similar thing with donkeys, cows, capybaras, rabbits, hamsters, penguins, humans… You name it. Just don’t make them black and red, they will look odd. There are also other features that OOP provides, like creating different classes and let one class fetch methods from another class (inheritance), we can inherit a class, and override a method to show different behaviour of that method (polymorphism).

So In OOP, you have an object, and you have data about that object. If you have multiple instances of a class, and you change the attributes of the first instance, you don’t affect the others.

Speaking of your code, if you need such representation, so that every object has unique data associated with them, then yes, you need to wrap that around a class. There’s a bad way to do that though, using Arrays, you can store Objects and their data (say zip object and data together probably with a Hash), but that makes stuff ugly, and that may also get slower.

…wondering if there is any advantage in putting a class wrapper around it.

So I think it depends.

If you need to use multiple fork or threads (or fibers, who knows), use simple functions (object methods, actually every thing is an object in Ruby, and there are no functions, only method calls) without wrapping them in a class, if that gets your work done and readable!


Performance:

If you need to call a class method like:

while true
	X.new.something
	sleep 0.001
end

I will not suggest using a class for these stuff, it will make your code slower. Just use either object methods, proc or lambdas or just write the code here. It will make your code much faster. I have benchmarked it and classes makes them way slower. If your code need something like this:

x = X.new
while true
	x.something
	sleep 0.001
end

You are probably fine, because you are calling method something on x, you are not creating new X objects, but x.something may create new objects, and may not, depends on the return value. It’s not much slower than the above example.


If you don’t yet know, Ruby also has a similar thing to a class, called modules, most people put their codes like dir_walk() in modules. Use your favourite search engine, and take a look at “ruby modules”…

To be quite honest if you’re writing small programs, you won’t fully comprehend the advantages of OOP and utilizing classes. For me I understood how to make a class in university but only truly utilized its advantages when making larger scale projects.

Essentially it’s to dish out numerous different types of objects. If your programs are small and your class is just used to create one or two objects, it might save you more time to hard code those objects individually. When I develop games, I tend to need the same function but rewritten(polymorphed) NUMEROUS times depending on the character or player using the function. I need to be able to dish out 30 monsters quickly and some need different values. That is when I find classes useful.

If it’s still confusing, try your best to understand it and if you want to practice it, make a hobby project which simulates some sort of game. Game dev will really solidify OOP concepts. Other than that, don’t force yourself to understand the ins and outs of classes and OOP until you have to use it. Like any tool, it may be a good tool but maybe not for certain situations.

It depends where you start from. If like me you began with procedural languages, when I started with Java I remember asking myself “do I really need a FileReader and a BufferedReader just to read a file?” Why can’t I just do

while (<>) {
   ...
}

like I do in perl? But if you’ve only ever used objects it seems perfectly natural. I see a lot of code written in OO languages that is really procedural, like calling static functions. But once you start using ‘proper’ objects that hold data and have behaviours, you won’t want to go back.

1 Like