Programming Languages

Object Oriented Programming in Ruby

Jim Posen - ECE/CS 2014

Object Oriented Programming

  • I assume everyone is familiar with it
  • What are the characteristics of OOP?

Words used in OOP

  • Classes
  • Methods
  • Instance variables
  • Class/static variables
  • Inheritance
  • Composition
  • Abstraction

Benefits of OOP

  • Polymorphism
  • Inheritance
  • Abstraction
  • Dynamic Dispatch

Non-exclusive benefits

  • Static typing
  • Code reuse
  • Design patterns
  • Encapsulation

Classes

  • Definition of a type
  • Definition of constructors
  • Definition of methods
  • Code executed in context of class

Class definition

  • Use the class keyword to define a class
class Pokemon
  ...
end

Constructor definition

  • The new method constructs/instantiates an object
  • The new method will be defined on the class already
  • new will call the initialize method
  • initialize takes whatever parameters you want
class Pokemon
  def initialize(trainer, level = 5)
    ...
  end
end

pikachu = Pokemon.new(ash, 7)
staryu = Pokemon.new(misty)

Method definition

  • Methods defined with def as always
  • self keyword refers to the calling object
class Pokemon
  def initialize(trainer, level = 5)
    ...
  end

  def lose_health(damage)
    ...
  end

  def attack(other)
    other.lose_health(100)
  end
end

pikachu.attack(staryu)

Instance variables

  • Instance variables store state within an object
  • Instance variables do not need to be declared
  • But they are often initialized in the constructor
  • All instance variables begin with @
class Pokemon
  def initialize(trainer, level = 5)
    @trainer = trainer
    @level = level
    @health = 100
  end

  def lose_health(damage)
    @health -= 100
  end

  def attack(other)
    other.lose_health(100)
  end
end

pikachu.attack(staryu)

Instance variable access

  • Need to define getters and setters for instance variables
class Pokemon
  ...
  def health
    @health
  end

  def health=(value)
    @health = value
  end
end

pikachu.health = 400
puts pikachu.health

attr_accessor

  • Helper methods for defining getter and setter methods
  • attr_reader defines getters
  • attr_writer defines setters
  • attr_accessor defines both
class Pokemon
  attr_accessor :health, :trainer, :level
  ...
end

Message passing

  • Ruby uses the message passing model of calling methods
  • Calling a method is like sending a message to an object with certain arguments
  • Calling dog.bark is like sending the “bark” message to a Dog object
  • Ruby lets you abstract out method invocation with the #send method
irb> dog = Dog.new
irb> dog.bark
"Woof"
irb> dog.send :bark
"Woof"
irb> message = :bark
irb> dog.send message
"Woof"

OOP done right

  • Everything in Ruby is an object
  • Yes, everything
  • We have seen the times method defined on numbers

Literals

  • Integer literals are Fixnums
  • String literals are Strings

Reflection

  • Classes are objects
  • Call the class method on any object to get its class
  • Classes extend the Class class (which is, of course, an object)
  • Classes can be assigned to variables
  • Far more natural than Java reflection
# Using the #constantize method defined in ActiveSupport in Rails
pokemon = "Pokemon".constantize.new

Class methods

  • Since each class is an object, classes can have methods defined on them
  • We have already seen the Class#new method
class Pokemon
  def self.known?(species)
    ...
  end
end

Pokemon.known? :pikachu

As a side note, Class not only has #new defined but ::new as well

my_class = Class.new do
  def foo
    "bar"
  end

  ...
end

Class variables

  • Class variables start with @@
  • Class variables are accessible from both class and instances

Inheritance

  • Rails does not support multiple inheritance
  • Classes inherit from Object by default
  • superclass method returns parent class
class Pikachu < Pokemon
  ...
end

Abstract classes

  • Classes do not need to be declared as abstract
  • They can simply make use of methods assumed to be defined in child classes
  • If you call the abstract method on the parent class, you will get an Exception
class Pokemon
  def initialize
    @health = initial_health
  end
end

class Pikachu < Pokemon
  def initial_health
    200 * level
  end
end

Interfaces

  • What about interfaces? Don’t we need them?
  • Nope, there is no static type checking
  • Interfaces are used to specify which objects have a certain method defined on them

Duck typing

  • In Ruby, we assume that if a method is implemented on an object, then it does what we want
  • If it quacks like a duck, it might as well be a duck
collection = ...
collection.each do |element|
  # We don't know what type of collection we are dealing with
  # But we don't care because we assume #each will iterate over the elements
  ...
end

Code reuse

  • What are our options for code reuse between classes?
  • Inheritance and composition
  • Single inheritance can limit code reuse
  • Interfaces (in Java) do not solve this problem

An example

  • Think about all iterable collections
  • As seen before, we will assume any object with the #each method defined is iterable
  • Every iterable collection should have map, filter, and reduce defined on them
class Iterable

  # Abstract #each method

  def map
    results = []
    self.each do |element|
      results << yield element
    end
    results
  end

  def filter
    results = []
    self.each do |element|
      results << element if yield element
    end
    results
  end

  def reduce(initial)
    self.each do |element|
      initial = yield initial, element
    end
    initial
  end
end

Despite being correct, there is no good way to reuse this functionality in classes that have a more logical parent class

Modules

  • Let’s define this functionality in a module instead of a class
  • Modules are not directly instantiable
module Enumerable
  def map
    ...
  end

  def filter
    ...
  end

  def reduce
    ...
  end
end

Modules

  • Let’s include the module in a class with each defined
  • Modules used in this way are called mixins
class CollectionOfSomeSort
  include Enumerable

  def each
    ...
  end
end

collection = CollectionOfSomeSort.new
collection.map { |element| [element, element] }

Reopening Classes

  • We want a utility to turn the singular form of a word into the plural form
  • This is used everywhere in Rails
  • What are our options?
    • We can make a StringUtils class
    • But we would really just love if String#pluralize was defined

Reopening Classes

Well, we can do just that

class String
  def pluralize
    if self =~ /s$/
      self
    else
      self + "s"
    end
  end
end

Use this only if you have a very good reason

OOP is good design

  • We have finally arrived at a real programming language
  • Now we can actually write large systems
  • And leverage all of our Gang of Four design patterns

Wrong!

The problem with CS 308

  • Please never think that
  • We have spent 11 weeks looking at various equally valid programming paradigms
  • Object oriented programming is one way you may choose to structure your code
  • It works well in some situations
  • Not so well in others
  • There are tradeoffs just like any other design decision