Ruby Classes

advertisement
types in Ruby and other
languages…
 Classes
and objects (vs prototypes)
 Instance variables/encapsulation
 Object creation
 Object equality/comparison
 Object type
• type-safety
• type conversions
 Class
methods
 Class variables
 Inheritance – another lecture
 Protect
instance variables from outside
forces (i.e., other classes)
 Ruby is strictly encapsulated
• always private
• you must include setters/getter
• as long as other classes can’t access data
directly, it’s encapsulated
 Reflection
and open classes (covered
later) subvert this encapsulation
 package
by default
 can declare public
 best practice: make instance variables
private
 why?
• provides data validity (can only update via
setter)
• if you want to change internal data
representation later, easy to do (other code
accesses via getter… )
 Every
class inherits method new
• calls allocate to get space (can’t override)
• calls initialize to create instance variables
 Often
convenient to provide default
parameters for initialize
def initialize(x, y, z=nil)
@x, @y, @z = x,y,z
end
class Cat
def initialize(name, age)
@name, @age = name, age
end
def to_s
"(#@name, #@age)"
end
def name
@name
end
c = Cat.new("Fluffy", 2)
puts c
puts c.name
c.age=5
puts c.age
puts c.to_s
new calls initialize
def age
@age
end
to_s like toString
getters and setters, called attributes
last expression in function is return
def name=(value)
@name=value
end
@ indicates instance variable
always refer to self
def age=(value)
@age=value
end
end
DO NOT declare outside of method
class Bottle
attr_accessor :ounces
attr_reader :label
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def add(other)
Bottle.new(@label, @ounces+other.ounces)
end
def add!(other)
@ounces += other.ounces
other.ounces = 0
end
def split(scalar)
newOunces = @ounces / scalar
@ounces -= newOunces
Bottle.new(@label, newOunces)
end
def +(amount)
Bottle.new(@label, @ounces + amount)
end
def to_s
# with an accessor, don't need @ - why?
"(#{label}, #{ounces})"
end
end
attr is example of metaprogramming
b = Bottle.new("Tab", 16)
puts b.label
b.ounces = 20
puts "b is #{b}“
puts "result of b2 = b.split 4"
b2 = b.split 4
puts "b is #{b}"
puts "b2 is #{b2}“
puts "result of b3 = b.add(b2)"
b3 = b.add(b2)
puts "b3 is #{b3}“
puts "result of b.add!(b2)"
b.add!(b2)
puts "b is #{b}"
puts "b2 is #{b2}“
puts "result of b4 = b3 + 10"
b4 = b3 + 10
puts "b4 is #{b4}"


From wikipedia:
Polymorphism (from the Greek many forms) is the
provision of a single interface to entities of different
types



If a function denotes different and potentially
heterogeneous implementations depending on a
limited range of individually specified types and
combinations, it is called ad hoc polymorphism.
Ad hoc polymorphism is supported in many languages
using function overloading.
Why would this be called ad hoc?
See also: http://stackoverflow.com/questions/154577/polymorphism-vs-overriding-vs-overloading


If the code is written without mention of any specific
type and thus can be used transparently with any
number of new types, it is called parametric
polymorphism.
In the object-oriented programming community, this is
often known as generics or generic programming.
See also: http://stackoverflow.com/questions/154577/polymorphism-vs-overriding-vs-overloading
class Can
attr_accessor :ounces
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def to_s
"(#@label, #@ounces)"
end
end

b = Bottle.new("Tab", 16)
c = Can.new(“Coke", 12)
b2 = b.add(c)
puts "result of b2 = b.add(c)"
puts "b2 is #{b2}“

cat = Cat.new
b3 = b.add(cat)

If it walks like a duck and
quacks like a duck, it
must be a duck
any object with needed
method will work
undefined method
‘ounces’ for <Cat>
http://beust.com/weblog/2005/04/15/the-perils-of-duck-typing/
 x=1
 x.instance_of?
 x.instance_of?
Fixnum => true
Numeric => false (even
though subclass)
 x.is_a?
 x.is_a?
 x.is_a?
 x.is_a?
Fixnum => true
Numeric => true
Comparable => true
Object => true
 x.class
 x.class
== Fixnum => true
== Numeric => false
Quick Ex: how does Java handle?
http://www.java2s.com/Tutorial/Java/0060__Operators/TheinstanceofKeyword.htm
 Class
of an object doesn’t change
 Type is more fluid… includes set of
behaviors (methods it responds to)
• i.e, Class != Type….whoaaa
 respond_to?
 equal?
checks addresses (like == in Java)
 for Object, == is synonym for equal?
 Most classes override == (like equal in Java)
 POPL
goal: be aware of features that commonly
exist in many/all languages BUT have subtle
differences
class Bottle
attr_accessor :ounces
attr_reader :label
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def ==(other)
return false if ! other.instance_of? Bottle
return @ounces == other.ounces && @label ==
other.label
end
alias eql? ==
end
# Can also has ounces
class Can
attr_accessor :ounces
def initialize(label, ounces)
@label = label
@ounces = ounces
end
end
b = Bottle.new("Tab", 16)
b2 = Bottle.new("Tab", 16)
b3 = Bottle.new("Tab", 12)
b4 = Bottle.new("Coke", 16)
puts "b.equal?(b2) #{b.equal?(b2)}"
puts "b == b2 #{b == b2}"
puts "b == b3 #{b == b3}"
puts "b == b4 #{b == b4}“
puts "b.eql?(b2) #{b.eql?(b2)}"
puts "b.eql?(b3) #{b.eql?(b3)}"
c = Can.new("Tab", 16)
puts "b == c #{b == c}"
 Numeric
classes will do type conversions when
used with ==
 eql? is like ==, but no type conversion
• 1.eql? 1.0
 can
alias eql? with ==
 ===
used with case statement
• (1..10) === 5 # true, 5 is in range
• /\d+/ === “123” # true, matches regex
• String === “s” # true if s is instance of String
Other language with eql?/=== feature?
class Bottle
attr_accessor :ounces
attr_reader :label
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def add(other)
raise TypeError, "Bottle argument expected " unless other.is_a? Bottle
Bottle.new(@label, @ounces+other.ounces)
end
def add2(other)
raise TypeError, "Bottle argument expected " unless other.respond_to?
:ounces
Bottle.new(@label, @ounces+other.ounces)
end
def add3(other)
Bottle.new(@label, @ounces+other.ounces)
rescue
raise TypeError, "Cannot add with an argument that doesn't have
ounces"
end
def to_s
"(#@label, #@ounces)"
end
end
class Can
attr_accessor :ounces
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def to_s
"(#@label, #@ounces)"
end
end
class Cat
end
b = Bottle.new("Tab", 16)
c = Can.new("Coke", 12)
puts "result of b2 = b.add(c)"
b2 = b.add(c)
puts "result of b2 = b.add2(c)"
b2 = b.add2(c)
puts "b2 is #{b2}"
puts "result of b2 = b.add3(c)"
b2 = b.add3(c)
puts "b2 is #{b2}"
cat = Cat.new
puts "result of b2 = b.add3(cat)"
b2 = b.add3(cat)
class Bottle
attr_accessor :ounces
attr_reader :label
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def each
yield @label
yield @ounces
end
def [](index)
case index
when 0, -2 then @label
when 1, -1 then @ounces
when :label, "label" then @label
when :ounces, "ounces" then @ounces
end
end
end
b = Bottle.new("Tab", 16)
puts "using each"
b.each {|x| puts x }
puts "[ix]"
puts b[0]
puts b[-2]
puts b[1]
puts b[-1]
puts "[field name]"
puts b["label"]
puts b[:label]
puts b["ounces"]
puts b[:ounces]
 Implement
<=> operator (spaceship)
 Like compareTo (-1, 0, 1 and nil if not
comparable)
 Typically include Comparable module as
a mixin
 Provides <, <=, ==, >=, >(defined in
terms of <=>)
 Sometimes write == anyway (e.g., one
may be case-sensitive, may be more
efficient). Best to be consistent.
class Bottle
include Comparable
attr_accessor :ounces
attr_reader :label
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def hash
#based on Effective Java by Bloch
code = 17
code = 37 * code + @label.hash
code = 37 * code + @ounces.hash
code
end
def <=>(other)
return nil unless other.instance_of? Bottle
@ounces <=> other.ounces
end
end
b = Bottle.new("Tab", 16)
b2 = Bottle.new("Tab", 12)
b3 = Bottle.new("Coke", 16)
b4 = Bottle.new("Tab", 16)
puts "b == b2 #{b == b2}"
puts "b < b2 #{b < b2}"
puts "b > b2 #{b > b2}"
puts "b == b3 #{b == b3}"
puts "b == b4 #{b == b4}"
class Bottle
include Comparable
attr_accessor :ounces
attr_reader :label
MAX_OUNCES = 64
@@numBottles = 0
def initialize(label, ounces)
@label = label
@@numBottles += 1
if ounces > MAX_OUNCES
@ounces = MAX_OUNCES
else
@ounces = ounces
end
end
bottles = Array.new
bottles[0] = Bottle.new("Tab", 16)
bottles[1] = Bottle.new("Tab", 16)
bottles[2] = Bottle.new("Coke", 16)
bottles[3] = Bottle.new("Tab", 20)
puts "Total ounces: #{Bottle.sum bottles}"
b = Bottle.new("Sprite", 72)
puts b
Bottle.report
def Bottle.sum(bottles)
total = 0
bottles.each {|b| total += b.ounces }
total
end
def to_s
"(#@label, #@ounces)"
end
def self.report #class method
puts "Number of bottles created: #@@numBottles"
end
end
Not covered: Class Instance Variables

Language Concepts
Classes
Instance variables
Object creation
Determine object type
Object equality
Object comparisons
Type conversions
Type safety
Class methods – next
lecture
• Class variables
•
•
•
•
•
•
•
•
•

Ruby
• new
• to_s
• duck typing
Not covered, but may be useful
 dup
 clone
 initialize_copy
 marshalling (create from serialized data)
Download