The Basics of Classes and Variables
Both versions define the following three classes:
- A class that represents the total number of occurrences of a word across all files
- A class that is derived from this class and extended to also maintain the occurrences of each word by file
- A counter class that reads and parses the files, creates and updates the word counts, and outputs the XML file
The first class is defined in Listing 1 at line 22 and in Listing 2 at line 4. Both implementations maintain a string word and a total_count variable. In Ruby, instance variables are preceded by a "@" symbol, and therefore Listing 2 has @word and a @total_count. Local variables have no prefix and global variables have a "$" symbol as a prefix.
The C++ code uses a struct to declare this class. Therefore, the variables word and a total_count are public by default. Ruby, however, does not allow access to instance variables from outside of the object; all instance variables are private. You'll learn more about access control later, but for now focus on adding the needed accessor methods. Luckily, as the statement at line 7 of Listing 1 demonstrates, adding these methods is no chore. You can automatically generate the needed get accessor methods by listing the variables after attr_reader.
Both implementations of this class also define a constructor that takes the word string, a method add that increments the counters, and a method file_occurrences that returns a data structure that holds per-file information. As shown on line 9 of Listing 2, the class constructors in Ruby are named initialize.
If you ignore the "include Comparable" in the Ruby code until later, the remainder of the implementation for this base class is then fairly straightforward for both languages.
Inheritance and Polymorphism
The next class defined in both files inherits from this simple base class, extending it to also track the total number of occurrence for the word in each file. The C++ implementation uses ": public word_count" to indicate the inheritance. The Ruby implementation uses "< WordCount". In both cases, the extended class adds a hash map to store the occurrence count associated with each processed file. The method add is extended to update the hash map, and the method file_occurrences returns this information.
There are few key differences between inheritance in C++ and inheritance in Ruby. Ruby, unlike C++, does not support multiple inheritance but does support mixins. The "include Comparable" found on line 16 of Listing 2 is an example. A Module is a set of function definitions. You can't create an instance of Module; you can only include it into class definitions. In this case, Module Comparable defines the comparison operators (<, >, ==) in terms of the <=> operator. So by defining <=> and including Module Comparable, you get the other comparison operators for free.
In C++, you sometimes rely on inheritance combined with virtual functions to enable polymorphism. A pointer x of type T * can point to an object of type T or any object with a type below T in the class hierarchy. A virtual method invoked through x is resolved by walking up the class hierarchy, starting from the type of the object pointed to by x.
Ruby on the other hand uses duck typingif something looks like a duck, swims like a duck, and quacks like a duck, then it's a duck. Take the following code for example:
For Ruby, it doesn't matter what type x is. If the object x has a method print_hello, the code will work. So unlike C++, which would require the objects to inherit from a common base type, you can pass objects of unrelated types to my_method, as long as they all implement print_hello.