Home > Development > Object Oriented Ruby

Object Oriented Ruby

May 2nd, 2016

I have been seeing a lot influence in ruby from Functional programming (short: FP), and not as such in ruby itself but in code written in ruby. Developers try new things, they are fascinated by other languages, how they solve problems. They try to change ruby into functional language, they loose sight of how problems can be solved in the Object Oriented Programming (short: OOP). Let’s do a refresher of some most important aspects of OOP.

Encapsulation

Technical details aside encapsulation is the most important concept in OOP. At the roots OOP was meant to be a simplified representation of real life things/objects/people, think a car, an animal, a plant. calculator For an example we will take an calculator, it’s a fairly complex thing. An calculator has a display, some buttons and some magic insights. The buttons are the input for calculator, the display is it’s output. We do not know what happens inside. Well some of us do, the point is only those interested in creating calculators know how to build one. For anyone else calculator has an interface with input and output allowing anyone knowing it’s language interaction with the calculator. This is encapsulation, most calculator users will use it’s interface, they do not need to know what happens inside. Is it an old kind of machine with gears, is it a computer, or maybe a new invention protein based? As long as it works as expected – nobody cares.

Mixins

When looking around we see a lot of repeating patterns. As software developers we do not want to repeat code, we write reusable methods, we put them in modules to group them. Mixins is where writing functional style code is important, we do not mutate any state, we take inputs and give outputs. I will illustrate on few examples what to avoid and how to write mixins code.

Example 1.

module ConfigReader
  def config
    @config ||= File.readlines(@config_file_name)
  end
end

This code breaks encapsulation, it assumes there is @config_file_name, it puts requirements on people that will use it, it does not define interface, it’s not functional. How would that be bad? Any mixin author could use @config, maybe there is Options module that also defines @config – you see how that could be bad? Authors of both modules would need to know of each other and would need to make sure they variables do not overlap.

Example 2.

module ConfigReader
  def read_config(file_name)
    File.readlines(file_name)
  end
end

In this example we do not assume existence of any variables, we do not define any variables. What we have here is a pure functional interface, no side effects (for the object).

If you notice the need to reference/write variables in mixins you should reconsider, you might need composition or inheritance.

Example 3.

module Age
  attr_accessor :birth_date
  def age
    (Date.today - @birth_date)/365
  end
end

It looks pretty contained, right? The thing is – it touches again on instance variables. This is definitively where composition would come in handy.

Composition

When we think of the world we know it consists of multiple objects, those objects can be often decomposed further into smaller objects down to atoms. In OOP composition helps us move between high level overview and implementation details, but as always it can be understood wrong, it’s very important to not forget about encapsulation. Similarly to mixins I will show some examples here too:

Example 4.

class Person
  class Barber
    attr_reader :person
    def initialize(person)
      @person = person
    end
    def dye_hair(color)
      person.name = color
    end
  end

  attr_accessor :name, :hair_color
  def initialize(name, hair_color)
    @name, @hair_color = name, hair_color
  end
  def barber
    Barber.new(self)
  end
end
michal = Person.new("Michal", "brown")
michal.name
=> "Michal"
michal.barber.dye_hair("blue")
michal.name
=> "blue"

In this example we show the importance of encapsulation in composition, we can not allow anybody to change everything inside of a class. The biggest mistake in this example is passing the instance to the other object with assumption it will operate on the passed instance. If we pass the parent it should only be for reading. How this could have been done differently?

Example 5.

class Hair < Struct.new(:color)
end
class Barber
  def dye_hair(hair, color)
    hair.color = "#{hair.color} #{color}"
  end
end
class Person
  attr_reader :name, :hair
  private :hair
  def initialize(name, hair)
    @name, @hair = name, Hair.new(hair)
  end
  def dye_hair(barer, color)
    barer.dye_hair(hair, color)
  end
  def hair_color
    hair.color
  end
end
barber = Barber.new
michal = Person.new("Michal", "brown")
michal.hair_color
=> "brown"
michal.dye_hair(barber, "blue")
michal.hair_color
=> "brown blue"

With this new example Barber can not change anything else but Hair. In composition it’s important to not break the encapsulation, using other objects, blocks or return values we can be much safer, we will not manipulate objects that are not our concern.

Inheritance

In OOP inheritance is where we extend the functionality in each inherited(child) class. Let a example show what inheritance gives us:

Example 6.

class Person
  attr_reader :strength
  def initialize(name, strength)
    @name, @strength  = name, strength
  end
  # @returns NUMBER overly simplified distance how far it's thrown
  def throw(weight)
    strength / weight
  end
end

class SuperHuman < Person
  def initialize(name, strength)
    super(name, strength * 1_000_000)
  end
  # @returns NUMBER overly simplified distance flown in given time
  def fly(time)
    strength * time
  end
end

In above example we see two traits of inheritance:

  1. We have access to the variables / methods of the class we are inheriting.
  2. We can add new functionality extending capabilities of the inherited class.

So inheritance crosses the encapsulation boundary … does it? Not really, inheritance happens before we instantiate the object. Think of inheritance like of onions or ogres ;) – it has layers, each child class builds on top of the parent class, it adds extra functions, it improves previous implementation, it does not reach between objects to change variables.

When thinking of using inheritance you need to distinguish between “is a kind of” and “has the same methods”. Inheritance only works when it’s “kind of” relation, in other cases consider mixins or composition depending on the use case.

Summary

It’s hard to pick proper way of structuring our code. With the above examples I tried to show where each of the described methods fits in. For me the biggest differentiatior is encapsulation, is the object maintaining it’s own state or let’s others do that?

Writing OOP code does not mean that you need to forget about FP, actually it’s reverse, it all connects together. OOP does not require you to change state, you can actually write most of your methods in functional way, making sure the code has strictly defined interface and uses only it’s input and produces output. It’s more mater of your codding culture, the language can not restrict or force you to write good code, it falls on the developer to structure code. This is why it’s good to go out of your comfort zone and learn other languages, why you need to experiment. But before you go out on an adventure with other concepts make sure you know your own backyard.

Conference talk / follow ups

I’m working on extending this topic in further posts and also started creating conference talk from it, if you are interested in hosting the talk contact me directly.

I’m also searching for new Ruby job, I had a great time with StackBuilders but the company is focusing on Haskell and I want to continue my work in Ruby world. So if you heard of somebody in need of good Ruby developer let me know.

Categories: Development Tags:
Comments are closed.