Created
January 8, 2013 08:31
-
-
Save hjc/4482204 to your computer and use it in GitHub Desktop.
A simple script that shows how Ruby's basic OO concepts work and some common errors.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Simple example of basic object oriented concepts and how they work in Ruby | |
# | |
# AUTHOR: HAYDEN CHUDY | |
# First is instance and class variable example | |
# parent class, keep track of the count of all its children | |
class Shape | |
# count class variable. Keeps count of all shapes we have made, in general | |
@@num_sh = 0 | |
# Abstract class, only init code it has is to increase its class count and set a | |
# destructor to lower its count. Will never be called directly, only through the | |
# super keyword. Also this abstract class has no parameters, but its children will, | |
# and all children will invoke super, passing their arguments to this function, | |
# which logically should take 0 arguments. If we leave this parameterless and | |
# call it from functions who do have parameters, we will get an invalid argument | |
# count error, so we must give this some parameters. However, a shape could take | |
# any number of arguments, as demonstrated below, so this function needs to be | |
# able to take any arguments as well, this is what *params does. It uses the splat | |
# operator to allow this function to work 0-* arguments... all of which are ignored | |
def initialize(*params) | |
@@num_sh += 1 | |
# clean up after yourself | |
ObjectSpace.define_finalizer(self, proc { @@num_sh -= 1 }) | |
# to demonstrate reflection, we will also have all generic shape classes | |
# print their class name, the names of their instance variables, and the | |
# values of those vars. However, we will only implement this code in the | |
# abstract parent Shape class, call it during super, and reflection will | |
# ensure we get the correct results | |
puts "Class is: " + self.class.to_s + " with parameters:" | |
params.each_with_index do |parameter, index| | |
puts "Param " + (index + 1).to_s + "(" + self.instance_variables[index].to_s + ")" + ": " + parameter.to_s | |
end | |
puts | |
end | |
# show count (called with Shape.count) | |
# Used to show how class variables work with subclasses | |
def self.count | |
@@num_sh | |
end | |
end | |
# first child class, counts its instances | |
class Square < Shape | |
# own class var for counting | |
@@num_sq = 0 | |
# init a square, set side lengths, increment number of squares, create a finalizer | |
# and call the parent method | |
def initialize(side_length) | |
@side_length = side_length | |
@@num_sq += 1 | |
# call parent method so we can increment the number of shapes, can do alternative: | |
# | |
# if defined?(@@num_sh) | |
# @@num_sh += 1 | |
# else | |
# @@num_sh = 1 | |
# end | |
# | |
# If desired. | |
super | |
# Create finalizer that reduces number of square. In Ruby objects can have | |
# multiple finalizers (which are called when the garbage man comes), so | |
# the finalizer defined in the shape class that lowers the number of shapes | |
# is given to each instance of the Square class when the super method | |
# is called, those we only handle counting squares here. Logic and encapsulation | |
# for the win. | |
ObjectSpace.define_finalizer(self, proc { @@num_sq -= 1}) | |
end | |
# two basic square methods | |
def area | |
@side_length * @side_length | |
end | |
def perimeter | |
@side_length * 4 | |
end | |
# return Square's count | |
def self.count | |
@@num_sq | |
end | |
end | |
# second child class, we will not count its instances. However, since it inherits | |
# from the Shape class and calls the Shape class's init method, it will increment | |
# the shape counters | |
class Triangle < Shape | |
# just init class and call parent method | |
def initialize(base_width, height, s1, s2, s3) | |
@base_width = base_width | |
@height = height | |
@side1 = s1 | |
@side2 = s2 | |
@side3 = s3 | |
super | |
end | |
# Misc methods | |
def area | |
@base_width * @height / 2.0 | |
end | |
# one line definition of a function. Ugly but space saving, not recommended | |
# for use. | |
def perimeter; @side1 + @side2 + @side3; end | |
end | |
# Test both types of objects | |
sq = Square.new(5) | |
puts "Area of square: " + sq.area.to_s | |
puts sq.perimeter | |
tr = Triangle.new(6, 6, 7.81, 7.81, 7.81) | |
puts tr.area | |
puts tr.perimeter | |
# Now test square and shape count | |
puts Square.count.to_s + " squares" | |
sq = Square.new(4) | |
puts Square.count.to_s + " squares" | |
puts Shape.count.to_s + " shapes" | |
# Method Visibility Example | |
class Person | |
def initialize(name) | |
set_name(name) | |
end | |
def name | |
@first_name + ' ' + @last_name | |
end | |
# we don't want our users to change names manually, only when they are set in | |
# the init | |
private | |
# all of these are fully private and cannot be called outside this object | |
def set_name(name) | |
fn, ln = name.split(/\s+/) | |
set_first_name(fn) | |
set_last_name(ln) | |
end | |
def set_last_name(last_name) | |
@last_name = last_name | |
end | |
def set_first_name(first_name) | |
@first_name = first_name | |
end | |
end | |
# Make a new person | |
per = Person.new("Fred Boggs") | |
p per.name | |
# Let's try to change some stuff and see if Ruby lets us | |
# perm error is name error, but reg exception class works too | |
begin | |
per.set_last_name("Smith") | |
rescue NameError => e | |
e.message | |
puts "NameError: ERROR!! SETTING FIELD: NAME FOR PERSON OBJECT" | |
rescue Exception => e | |
puts e.message | |
puts "Exception: ERROR!! SETTING FIELD: NAME FOR PERSON OBJECT" | |
end | |
# regular Exception's work too | |
begin | |
per.set_name("Smith") | |
rescue Exception => e | |
puts e.message | |
puts "ERROR!! SETTING NAME" | |
end | |
# Polymorphism and Constants Example | |
class Animal | |
Genome = "None" | |
def initialize(name) | |
@name = name | |
end | |
end | |
class Cat < Animal | |
Genome = "Feline" | |
def talk | |
"Meaow!" | |
end | |
end | |
class Dog < Animal | |
Genome = "Canine" | |
def talk | |
"Woof!" | |
end | |
end | |
# every animal class has its own Genome and talk method | |
animals = [Cat.new("flossie"), Dog.new("Rex"), Cat.new("Kitty")] | |
# as we iterate through each animal in the area, we have it talk and print its | |
# class's genome | |
animals.each do |ani| | |
puts ani.talk | |
puts "This animal's genome through each: " + ani.class::Genome | |
puts | |
end | |
# demonstrate referencing constants and changing them | |
puts "Constants are not on a global scope, but rather a classwide one, that is\ | |
subclasses override their parent's globals" | |
puts "Animal::Genome : " + Animal::Genome | |
puts "Dog::Genome : " + Dog::Genome | |
puts "Cat::Genome : " + Cat::Genome | |
# simple show on constant referencing | |
puts "Constants are referenced like: Class::Constant" | |
puts Animal::Genome | |
begin | |
puts Animal.Genome | |
rescue | |
puts "ERROR!!! Cannot use Animal.Genome" | |
end | |
puts | |
# Nested classes | |
class Drawing | |
def self.new_circle | |
Circle.new | |
end | |
class Line | |
end | |
class Circle | |
def what_am_i | |
"A Circle" | |
end | |
end | |
end | |
# The Line and Circle class only exist within the Drawing class. Any attempt | |
# to create or use them outside of the Drawing class will fail. This can be | |
# worked around by using Drawing::Line.method() for our various methods. | |
# This special method was designed to give us a Circle, we'll get one | |
a = Drawing.new_circle | |
puts a.what_am_i | |
# We can also get one by using the Drawing namespace, like so: Drawing::Circle | |
# This will work | |
b = Drawing::Circle.new | |
puts b.what_am_i | |
# Just doing Circle.new will not, as this shows | |
begin | |
c = Circle.new | |
rescue Exception => e | |
puts e.message | |
puts "ERROR!!! 'c = Circle.new' fails" | |
else | |
puts "Making C was fine, let's output it" | |
puts c.what_am_i | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment