OOP

Just the Data

The fundamental idea behind Object Oriented Programming is the unification of methods and data. When dealing with data in an OO manner it should never be uncoupled from the object which gives it meaning.

For example returning a String from an object that contains the word Red, even though it is wrapped in a literal object is complete useless. It has no context. Is it color? A name? A identifying code? Without coupling this value to an object, such as car.color the data is meaningless.

Data Abstraction

When creating a class to represent a real world object we can use data abstraction to the model that real object more effectively. This means creating classes so that their data is structured in such a way that it matches the real world object’s structure.

For example if we have a Bird object we can create a generalised blueprint that matches the structure of how information about a bird might be represented.

class Bird
  GRAVITY = 9.8
  def initialize(wing_span, weight)
    @wing_span = wing_span
    @weight = weight
    @position = { x: 0, y: 0 }
  end
  
  public
  def fly
    # use flying speed to move the bird and make it fly
  end

  private
  def flying_speed
    # code using gravity, weight and wing span
  end
end

Here the Bird class represents a bird with a position, wing_span and weight, everything need to calculate the bird’s flying_speed. We then include a fly to change the bird’s position.

Encapsulation

Encapsulation means structuring objects so that only the properties that are required to interact with the object are exposed to the rest of the program giving your code a standard interface with which to interact with a class. With encapsulation we hide all of the implementation details from the rest of code keeping it as a contained object.

In the above example GRAVITY is a constant that can be accessed for the Bird class using Bird::GRAVITY but not changed, this is useful if we were perhaps modelling an entire woodland and wanted to get a consistent gravity across the entire model.

The @position,@wing_span and @weight instance variables cannot be accessed from outside the Bird class because the rest of program does not need to change these directly. Furthermore the flying_speed method is kept private so that only a particular instance of the Bird class can calculate its flying speed because the rest of our code doesn’t need to interact with this method.

The fly method however is exposed to the rest of our code so that we have an interface for interacting with our Bird instances to make them fly!

Because our code has clearly defined channels for interacting with our Bird class it means we can easily change the implementation of the class without effecting the rest of our code. For example if we wanted to completely rewrite the fly method to changed how we calculating flying we can do that without in any way changing how the rest of our code outside of the Bird class works.

Single Responsibility Principle

SRP means structuring our program so that each individual part of its functionality is encapsulated as a distinct entity (or class) capable of handling that responsibility without outside assistance. Each class should have one logical thing that is responsible for.

This also means that if changes are required to your code they should only effect one component class in your system. If you find that writing a change into your system is propagating out to multiple classes in then you have probably violated SRP.

The code below describes a class Body which has several different methods for things like thinking, moving and looking around. Currently the Body class violates SRP because it is responsible for multiple components and behaviours in our system. Furthermore, if we want to make a change to “thinking” or “moving” then both of those changes require use to change the body class, rather than a single component in our system further violating SRP.

class Body
  def think
    # has thoughts
  end

  def move
    # moves around
  end

  def look
    # looks around
  end
end

Here is another version of the body’s functionality refactored to adhere to SRP. In this version the body class has been changed to function simply as a container for the new classes which function as logical units to contain the functionality that was originally in the Body class. Now, if we need to make changes to “thinking”, “moving” or “seeing” we only need to change one part of our code that is logical separated from the other elements of our program.

class Body
   def initialize
     @muscles = Muscles.new
     @brain = Brain.new
     @eyes = Eyes.new
   end
end

class Muscles
  def move
    # move around
  end
end

class Brain
  def think
    # thinks thoughts
  end
end

class Eyes
  def look
    # looks around
  end
end

We could go further with this principle. For example, what if we want to distinguish between different parts of movement such as moving the legs to walk, or moving the arms to wave? We don’t want to put the code for the walking and waving into the Muscles class directly because if we wanted to change our walking or waving code it would mean having more than one reason to change the Muscles class, thus violating SRP. Instead we should spin out this functionality into Legs and Arms classes that are simply managed by inherit from the Muscles class. That way if we need to change the way muscles work in general we can refactor the Muscles class - a single responsibility - but if we need to change the way our Body “walks” or “waves” we can change the Legs and Arms classes respsectively.

class Body
   def initialize
     @muscles = Muscles.new
     @brain = Brain.new
     @eyes = Eyes.new
   end
end

class Muscles
end

class Legs < Muscles
  def walk
    # walks around
  end
end

class Arms < Muscles
  def wave
    # waves at friends
  end
end

class Brain
  def think
    # thinks thoughts
  end
end

class Eyes
  def look
    # looks around
  end
end

You can also conceptualise SRP further as being method specific if you have a method within a class that has more than one responsibility then this also violates SRP and should be refactored into multiple methods.

Cohesion

Cohesion is similar to both encapsulation and SRP, it simply refers to grouping code in a way that is self contained and executes singular tasks well as opposed to spreading out the functionality of your code base.

Forwarding and Delegation

Forwarding and delegation are very similar. They both involve passing messages that are passed to a class down to other objects and using the methods on those objects instead. The main difference is that when you forward the class receiving the message is has its own entirely separate context and interface from the class sending the method. Whereas when you delegate the receiving class is wrapped inside the class calling it, so that the context of code remains consistent with the calling object.

One analogy used is that of receiving an email asking you to donate to a charity. If you forward the email you might send it to a friend who would be able to donate that amount. It’s not a personally responsibility. However, if you delegate the task of donating to your accountant then its still ultimately your responsibility to pay the money to the charity.

But, what does this actually look like?

The essential difference is how classes interact. The example below shows how delegation works. In this example the Phone class delegates the act of displaying things on the screen to the Screen class by storing an instance of it in the @screen instance variable. It has an essentially private relationship with the screen which it delegates responsibilities to. This is delegation because it is never exposed the context of @screen is never exposed publicly, it is all mediated through the display method in the phone object.

class Phone
  def initialize
    @screen = Screen.new
  end

  def display
    @screen.display
  end
end

class Screen
  def display
    # display stuff
  end
end

On the other hand forwarding is when the interface and context of the object that is handling things is exposed.

class Client
  def initialize(server)
    @server = server
  end
end

class Server
  def data
    # return server data
  end
end

In the code above the Client class takes in a Server object when created. This means that Server object exists in its own right even before the Client is first initialized. Then if our Client wants to get data it will forward that request to the server object. An example in the irb might be:

server = Server.new
client = Client.new(server)
client.server.data

Here we are calling the namespace of the server object directly and forwarding the request for data onto that object. It’s this distinction that is the primary difference between these two methods of object communication. In essence however they are both about extracting functionality and organising code.

Polymorphism

Polymorphism is the method of presenting the same interface on different objects, so that, even though the objects have different functionality the way code interacts with them is kept consistent meaning that we can use only a few lines of code to produce wildly different results simply by calling on different objects with the same interface.

class Taster
  def taste(food)
    puts food.flavor
  end
end

class Cabbage
  def flavor
    "Tastes like old socks"
  end
end

class Rice
  def flavor
    "Has the flavor of cardboard"
  end
end

class Almond
  def flavor
    "Like a nut party in my mouth"
  end
end

taster = Taster.new
taster.taste(Cabbage.new) # => "Tastes like old socks"
taster.taste(Rice.new) # => "Has the flavor of cardboard"
taster.taste(Almond.new) # => "Like a nut party in my mouth"

In the above example we can see the Taster class calls the the functionality on the different types of food in exactly the same way with the line food.flavor to produce wildly different outputs. This is because each of the food classes have a polymorphic relationship in which they all implement the same method flavor but with different outputs. Thus we only need one line of code in the Taster class to interact with all of these classes.

Inheritance

Inheritance is the method of defining multiple objects based on common sets of functionality that would be implemented on those objects. In the last example we created a set of polymorphic classes however its clear that there is a category relationship between all of these objects. They are all food. Which means they can inherit from a Food class.

class Food
  def initialize(type, color)
    @type = type
    @color = color
    @edible = true
  end
  
  def flavor
    "It tastes like something"
  end
end

class Cabbage < Food
  def flavor
    "Tastes like old socks"
  end
end

class Rice < Food
  def flavor
    "Has the flavor of cardboard"
  end
end

class Almond < Food
  def flavor
    "Like a nut party in my mouth"
  end

  def crunch
    puts "Crunchy nuts"
  end
end

We have now created a shared initialize method for each of foods that records their type and color as well as a boolean that describes them as edible. Furthermore each food sub class (Cabbage, Rice, Almond) overloads the flavor method defined in the Food class. This allows us to organise our code and centralise changes to the parent class of these items. For example, if we wanted to change how the initialize method on each of these classes without them inheriting from Food we would need to change three different constructors - not very dry or efficient. By letting our classes inherit the constructor we only have to change one piece of code to update all the subclasses.

Composition over Inheritance

Composition is process of designing the different parts of your code based on what they do or what they have rather than by what they are. In a traditional inheritance in object oriented programming, we manage classes based on classifying them as objects that are something.

InheritanceComposition
What something is - a computer is a type of machineWhat something does - A computer displays stuff
 What it has - a computer has a screen component that displasys stuff

Considering the inheritance and class chart below, imagine that we were asked to make a “robot muder dog” that can drive, murder and bark but not poop because it is a robot. We would be in a very bad situation because there is no way we can redefine this inheritance structure to include this functionality without bundling a lot of functionality that we don’t want - by creating a common inheritor for everything - or confusing our types. This is sometimes called the gorilla and banana problem because when ask for a banana - i.e. a small piece of added functionality - we instead get a gorilla holding a banana - i.e. implementing that small piece of functionality means we need to include a lot of unnecessary functionality in that class.

Animal
| .poop()
|
| Dog
| | .bark()
|
| Cat
| | .meow()

Robot
| .drive()
|
| Cleaner
| | .clean()
|
| Murderer
| | .murder()

We can solve the above problem using composition by extracting the different pieces of functionality into their own classes. Essentially separating these pieces of functionality in an almost modular fashion.

class Drive
  def drive
    "drives recklessly"
  end
end

class Clean
  def clean
    "cleans carefully"
  end
end

class Murder
  def kill
    "kills indiscriminately"
  end
end

class Poop
  def poop
    "poops violently"
  end
end

class Bark
  def bark
    "barks loudly"
  end
end

class Meow
  def meow
    "meows gently"
  end
end

Now our original classes and desired features can be created by simply slotting together the different pieces of functionality we have created in a “has a” relationship where the larger class structures contain these smaller functional classes and then use them to do things.

class Cat
  def initialize
    @meower = Meow.new
    @pooper = Poop.new
  end

  def meow
    @meower.meow
  end

  def poop
    @pooper.poop
  end
end

class RobotMurderDog
  def initialize
    @murderer = Murder.new
    @barker = Bark.new
    @driver = Drive.new
  end

  def murder
    @murderer.murder
  end

  def bark
    @barker.bark
  end

  def drive
    @driver.drive
  end
end

Now we can easily create our RobotMurderDog without introducing any unnecessary code into the class. We have kept our code try by extracting actions like barking and pooping into their own classes and can compose them in any way that we want.

Tell, Don’t Ask

“Tell, Don’t Ask” means telling an object what you want and letting it handle how to accomplish that as opposed to querying the state of an object and then calling methods based on the results of that test.

# Not so good
if user.admin?
  puts user.admin_message
else
  puts user.user_message
end

In the above example, this code queries whether the object is an admin? and then calls different user or admin specific message methods based on the result of that. This is a clear violation of TDA. As a caller we simply want to tell the user object to return the appropriate message. It is the responsibility of the called object to manage what is the correct output for that call. So instead we can refactor the User class to run a conditional inside a method called message that does this logic itself. Then when we call the .message method from outside the class it is clear and declarative.

# better
puts user.message

The Pragmatic Programmer describes this way of coding as “Think[ing] declaratively not procedurally.” and that it arises naturally if you design classes based on their responsibilities not their function.

Written with StackEdit.