Data abstraction is fundamental to most object oriented language - wherein the classes are designed to encapsulate data and provide methods to control how that data is modified (if at all), or helper methods to derive meaning of that data.
Ruby's Array class is an example of Data Abstraction. It provides a mechanism to manage an array of Objects, and provides operations that can be performed on that array, without you having to care how internally it is organized.
arr = [1,3,4,5,2,10]
p arr.class # Prints Array
p arr.sort # Prints [1,2,3,4,5,10]
Procedural abstraction is about hiding implementation details of procedure from the user. In the above example, you don't really need to know what sorting algorithm sort
method uses internally, you just use it assuming that nice folks in Ruby Core team picked a best one for you.
At the same time, Ruby may not know how to compare two items present in the Array
always. For example, below code would not run as Ruby does not know how to compare strings and numbers.
[1,3,4,5,"a","c","b", 2,10].sort
#=> `sort': comparison of Fixnum with String failed (ArgumentError)
It allows us to hook into implementation and help with comparison, even though underlying sorting algorithm remains same (as it is abstracted from the user)
[1,3,4,5,"a","c","b", 2,10].sort { |i,j|
if i.class == String and j.class == String
i <=> j
elsif i.class == Fixnum and j.class == Fixnum
i <=> j
else
0
end
}
#=> [1, 3, 4, 5, 2, 10, "a", "b", "c"]
When writing code for your own problems, procedural abstraction can be used to ensure a procedure often breaks down its problem into sub-problems, and solves each sub-problems using separate procedure. This allows, certain aspects to be extended later (as in above case, comparison could be extended - thanks to Ruby blocks
, it was much easier). Template method pattern is good technique to achieve this.