[Basics] 6 More on Objects

Object Oriented...

    Since we have now covered variables and constants. Lets have a little fun. 

    I want to give a overview and a spoiler if you will, on Objects and how they work in Ruby. For this post I would like to first talk methods. 

We haven't discussed methods very much however we have been using an atomic load of them within the previously shared posts. Lets view a quick reminder. 

>> Kernel.puts("The puts method is exposed via the Kernel Module")
The puts method is exposed via the Kernel Module

    Above, we call on the puts method of the Kernel Module. Puts is the equivalent of the print in most other languages. The puts method outputs data to Standard Out by default. The Kernel Module is exposed to the Ruby environment by being included as a Mix-In to the Object class. I can simply type the puts method alone and accomplish the same thing due to this exposure of the Kernel Modules functions.

>> puts("The puts method is exposed via the Kernel Module")
The puts method is exposed via the Kernel Module


    A simple Ruby method generally is created by a def statement followed by the method name, optionally followed by the comma delimited list of argument variables this method accepts (enclosed in parenthesis). Methods have the similar naming conventions as local variables accept they can end with characters like =, >, ?, and so on. I will define a super simplified method of multiplication in a procedural manner.

def simple_multiply(x,y)
    product = (x * y)
    product
end

>> simple_multiply(10,10)
=> 100

    Just for consistency, stop and look at how much effort will go into a procedural manner of using more complicated mathematical functions. Perhaps something like a permutation? This may quickly become nightmarish to the procedural programmer. For the object oriented programmer however, this is a walk in the park. 

    Knowing that the Ruby standard library provides a Math Module with many methods already built to perform well known commonly used math operations. Lets take a look at some of the methods we will find in the Math module.

#Cosine
>> Math.cos(6)
=> 0.960170286650366

#Tangent
>> Math.tan(10)
=> 0.648360827459087

#Arc Tangent
>> Math.atan(1.5)
=> 0.982793723247329

#Square Root
>> Math.sqrt(9)
=> 3.0

All of these functions take various numeric objects and return a new numeric object after performing the requested operation

>> Math.atan(1.5).class
=> Float

This method returns a floating point number, which is one with a decimal place. This is different than a Fixed Number which is just a simple whole number. There are methods which can convert numbers from type to type, (type casting).

#Converting the Floating point number to an integer rounds it DOWN to 0.

>> Math.atan(1.5).to_i 
=> 0

#Calling the class method on this object shows it is a Fixed Number and  no longer a Floating Point value.

>> Math.atan(1.5).to_i.class
=> Fixnum

For this brief method introduction that covers it. Methods unlike the simple one I showed for multiplication are generally designed to be re-usable. Like the ones I called on in the Math module, you can rely on these functions to be available to your code and SHOULD be used to perform these type of mathematical operations.

    Unlike other non object oriented languages, Rubies Methods, Variables, and Constants logically reside in Classes. Although Ruby is an object oriented language, it does NOT have to be used this way. Ruby can be used for procedural or linear coding styles. Taking the below example for reference...

    This is a paraphrasing of an example I have read in a very good book called, "Beginning Ruby" by Peter Cooper published by apress. That said, I am mainly summarizing the story as I am not that creative =), however the code in the examples is my own (for what it's worth).

Your manager says create a program which determines the areas of a basic Triangle. Lets say that the formula is simply base length multiplied by height divided by 2. Mathematically this can be expressed by the following formula.

x = base length
y = height
a = area
a = ( x * y ) / 2 

    The procedural programmer may now write up something like the below function determining the area of a triangle: 

#!/usr/bin/env ruby

t_length = ARGV[0]
t_height = ARGV[1]

def area_of_triangle(base_length,height)
    result = (base_length * height) / 2
end

def print_area
    puts "#{Triangle Area is: area_of_triangle(t_length,t_height)}"
end

print_area()


In which he has developed, very simply, a procedure for processing our formula given 2 variables (base length and height) and printing it out.  

The procedural style deals with the concepts and the methods of accomplishing a task. In order to add additional features we would often need to re-factor our code to meet current demands. We would need to add a new function which handles the new conditions, this can quickly get VERY ugly and hard to maintain.

    Lets take a look at the a different paradigm, the object oriented method of doing this. (The concepts used here have not been explained in full, just take it for what it is now.) 

#Lets define a Class named "Shape", let that class have a descendant named "Triangle". The Triangle class will posses the concept creating a Triangle and determine the area of one.

class Shape
end

class Triangle  < Shape
    def initialize(base_length,height)
        @length = base_length
        @height = height
    end

    def area(length,height)
        area = (length * height) / 2
    end
end

    We can now create a Shape Object (an instance of the Shape class) by simply calling on the new function. Notice that I did not define a new method! This is a real word example of Class Inheritance (More about this later).
   
#Creating a Shape Object using our Triangle Class.

>> tri = Triangle.new(1,2)
=> #<Triangle:0x7f55007e59d0 @height=2, @length=1>

So now our procedural developer and our object oriented developer have about the same code functionality in terms of implementing the algorithm for determining the triangles area. 

    However lets say that the project manager one day asks both programmers to make 5 shapes and output the area of each using their functions. Below would be the method for a procedural developer. 


#To create some Triangles in a procedural way. 

>> triangle = { 'base_length' => 10, 'height' => 10 }
=> {"base_length"=>10, "height"=>10}
>> triangle1 = { 'base_length' => 20, 'height' => 10 }
=> {"base_length"=>20, "height"=>10}
>> triangle2 = { 'base_length' => 20, 'height' => 10 }
=> {"base_length"=>20, "height"=>10}
>> triangle3 = { 'base_length' => 30, 'height' => 10 }
=> {"base_length"=>30, "height"=>10}

#To get the area of them in a procedural way. 

>> area_of_triangle(triangle["base_length"],triangle["height"])
=> 50
>> area_of_triangle(triangle1["base_length"],triangle["height"])
=> 100
>> area_of_triangle(triangle2["base_length"],triangle["height"])
=> 100
>> area_of_triangle(triangle3["base_length"],triangle["height"])
=> 150


Lets observe the object oriented method of this same act, which is faster by orders of magnitude.

#Create a Triangle Object, call the area method on it.

>> Triangle.new(10,20).area(10,20)
=> 100
>> Triangle.new(20,20).area(20,20)
=> 200

    With our Triangle class defined, creating trying objects and calling methods on those objects is a breeze. All of our concept is handled by the objects class. All we need to know is what is the bottom length, and height of our triangle.


Not convinced? OK lets perform some bulk operations.

    Lets take an array of 1 to 100 and counting down from 100 by 1's, pull 2 values each to be base length and height for our triangle and calculate the area.

#A range is a type in ruby, ranges can be numerical or alphabetic.

>> range = (1..100)
=> 1..100

#We call the to_a (short for to array method) to convert our Range to an Array.

>> array_from_range = range.to_a

#We now iterate that array by 2's creating triangle objects from the results and determining the area. 

>> while i < array_from_range.length do
?>     base_height, length = array_from_range.pop(2)
>>     puts "Triangle with base height #{base_height} and lenght of #{length} has an area of: #{Triangle.new(base_height,length).area(base_height,length)}"
>>   end
Triangle with base height 99 and lenght of 100 has an area of: 4950
Triangle with base height 97 and lenght of 98 has an area of: 4753
Triangle with base height 95 and lenght of 96 has an area of: 4560
Triangle with base height 93 and lenght of 94 has an area of: 4371
Triangle with base height 91 and lenght of 92 has an area of: 4186
Triangle with base height 89 and lenght of 90 has an area of: 4005
Triangle with base height 87 and lenght of 88 has an area of: 3828
Triangle with base height 85 and lenght of 86 has an area of: 3655
Triangle with base height 83 and lenght of 84 has an area of: 3486
Triangle with base height 81 and lenght of 82 has an area of: 3321
Triangle with base height 79 and lenght of 80 has an area of: 3160
Triangle with base height 77 and lenght of 78 has an area of: 3003
Triangle with base height 75 and lenght of 76 has an area of: 2850
Triangle with base height 73 and lenght of 74 has an area of: 2701
Triangle with base height 71 and lenght of 72 has an area of: 2556
Triangle with base height 69 and lenght of 70 has an area of: 2415
Triangle with base height 67 and lenght of 68 has an area of: 2278
Triangle with base height 65 and lenght of 66 has an area of: 2145
..
and so on and so on...
=> nil


He can also quickly define new functions which may come in as feature requests for his shapes. For instance...

    The manager NOW (They can never make their damn minds up any way) says he wants a way to count how many triangle shape objects are present. 

While the procedural developer has a slight heart attack because he is still dealing with how to manage many shapes in bulk, he now is faced with a new hacky ugly addition to his code. 

    The Object Oriented developer however smirks. He says to himself.. 

"I will use a Class variable to store the count and a Class method to access the count." 

Here is the new code. 

class Triangle  < Shape
    @@count = 0 
    def self.count
        @@count
    end

    def initialize(base_length,height)
        if defined?(@@count)
            @@count += 1
        else
            @@count = 1
        end

    def area(length,height)
        area = (length * height) / 2
    end
    
end


He edits the initialize method to check if @@count is defined, if not he sets it to 1 because apparently we initialized one triangle now. Else if it is defined, we add 1 more to the value. He then creates a Class method,  using the syntax self.count. This method returns the @@count variables value. 

#Define an range. 

range = (1..10)
=> 1..10

#Set a counter variable for iteration.

>> i = 0
=> 0

#Convert the range to an array. 

>> array = range.to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

#Creating some Triangle Objects

>> while i < array.length do 
?>     base_height, length = array.pop
array.pop
?>     base_height, length = array.pop(2)
>>     puts "Triangle with base height #{base_height} and lenght of #{length} has an area of: #{Triangle.new(base_height,length).area(base_height,length)}"
>>   end
Triangle with base height 9 and lenght of 10 has an area of: 45
Triangle with base height 7 and lenght of 8 has an area of: 28
Triangle with base height 5 and lenght of 6 has an area of: 15
Triangle with base height 3 and lenght of 4 has an area of: 6
Triangle with base height 1 and lenght of 2 has an area of: 1
=> nil

#How many Triangle Objects do we now have?
>> Triangle.count
=> 5


Comments