Skip to content

Instantly share code, notes, and snippets.

@hjc
Created January 8, 2013 08:31
Show Gist options
  • Save hjc/4482204 to your computer and use it in GitHub Desktop.
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.
# 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