Ruby is a purely object-oriented language, where everything in Ruby appears as an object. Every value in Ruby is an object, even the most primitive things: strings, numbers, even true and false are objects. The class itself is also an object, an instance of the Class class. This chapter will explain all the major features related to Ruby's object-oriented programming.
\n\nClasses are used to specify the form of an object; they combine data representation and methods, organizing data into a neat package. The data and methods within a class are called members of the class.
\n\nWhen you define a class, you are essentially defining a blueprint for a data type. This doesn't actually define any data, but rather defines what the class name means, that is, what an object of the class will consist of and what operations can be performed on that object.
\n\nClass definitions begin with the keyword class, followed by the class name, and end with an end to terminate the class definition. For example, we use the keyword class to define a Box class as follows:
\n\nclass Box\n #.. code\nend\n\nBy convention, names must start with a capital letter, and if the name contains multiple words, each word's first letter is capitalized, but with no separator (e.g., CamelCase).
\n\nClasses provide the blueprint for objects, so basically, objects are created from classes. We declare objects of a class using the new keyword. The following statements declare two objects of the class Box:
\n\nbox1 = Box.new\nbox2 = Box.new\n\nThe initialize method is a standard Ruby class method and works similarly to a constructor in other object-oriented programming languages. It is useful when you want to initialize some class variables at the time of object creation. This method takes a list of parameters, and like any other Ruby method, it must be preceded by the def keyword, as shown below:
\n\nclass Box\n def initialize(w,h)\n @width, @height = w, h\n end\nend\n\nInstance variables are class attributes that become object attributes when an object is created using the class. Each object's attributes are assigned individually and are not shared with other objects. Inside the class, these attributes are accessed using the @ operator, and outside the class, they are accessed using public methods called accessor methods. Below, we take the previously defined class Box as an example, using @width and @height as instance variables of the class Box.
\n\nclass Box\n def initialize(w,h)\n @width, @height = w, h\n end\nend\n\nTo read the variables defined in the class from outside the class, we can define accessor (getter) methods. The following example demonstrates the use of accessor methods:
\n\nExample
\n\nclass Box\n def initialize(w,h)\n @width, @height = w, h\n end\n def printWidth\n @width\n end\n def printHeight\n @height\n end\nend\n\nbox = Box.new(10, 20)\n\nx = box.printWidth()\ny = box.printHeight()\n\nputs "Box width : #{x}"\nputs "Box height : #{y}"\n\n\n\nWhen the above code is executed, it produces the following result:
\n\nBox width : 10\nBox height : 20\n\nSimilar to accessor methods used to access variable values, Ruby provides a way to pass parameters into variables defined in the class from outside the class, known as setter methods, defined as follows:
\n\nExample
\n\nclass Box\n def initialize(w,h)\n @width, @height = w, h\n end\n def getWidth\n @width\n end\n def getHeight\n @height\n end\n def setWidth=(value)\n @width = value\n end\n def setHeight=(value)\n @height = value\n end\nend\n\nbox = Box.new(10, 20)\nbox.setWidth = 30\nbox.setHeight = 50\n\nx = box.getWidth()\ny = box.getHeight()\n\nputs "Box width : #{x}"\nputs "Box height : #{y}"\n\n\n\nWhen the above code is executed, it produces the following result:
\n\nBox width : 30\nBox height : 50\n\n\n\n\nBecause both methods are very commonly used, Ruby defines **attr_accessor :variable_nameγattr_reader :variable_nameγattr_writer :variable_name** Three attribute declaration methods. Among them:**accessor=reader+writer**γ
\nAlso note: Variable name must be preceded by : οΌUse between variable names , Split.
\n
Instance methods are defined like any other method using the def keyword, but they can only be used through class instances, as shown in the following example. Their functionality is not limited to accessing instance variables; they can perform other tasks as needed.
\n\nExample
\n\nclass Box\n def initialize(w,h)\n @width, @height = w, h\n end\n def getArea\n @width * @height\n end\nend\n\nbox = Box.new(10, 20)\na = box.getArea()\nputs "Area of the box is : #{a}"\n\n\n\nWhen the above code is executed, it produces the following result:
\n\nArea of the box is : 200\n\nClass variables are variables shared across all instances of a class. In other words, instances of class variables can be accessed by all object instances. Class variables are prefixed with two @ characters (@@) and must be initialized within the class definition, as shown in the following example.
\n\nClass methods are defined using def self.methodname() and end with the end delimiter. Class methods can be called using the form classname.methodname, as shown in the following example:
\n\nExample
\n\nclass Box\n @@count = 0\n def initialize(w,h)\n @width, @height = w, h\n @@count += 1\n end\n def self.printCount()\n puts "Box count is : #@@count"\n end\nend\n\nbox1 = Box.new(10, 20)\nbox2 = Box.new(30, 100)\nBox.printCount()\n\n\n\nWhen the above code is executed, it produces the following result:
\n\nBox count is : 2\n\nAny class you define has a to_s instance method that returns a string representation of the object. Here is a simple example representing a Box object based on width and height:
\n\nExample
\n\nclass Box\n def initialize(w,h)\n @width, @height = w, h\n end\n def to_s\n "(w:#@width,h:#@height)"\n end\nend\n\nbox = Box.new(10, 20)\nputs "String representation of box is : #{box}"\n\n\n\nWhen the above code is executed, it produces the following result:
\n\nString representation of box is : (w:10,h:20)\n\nRuby provides three levels of protection for instance methods: public, private, or protected. Ruby does not apply any access control to instance and class variables.
\n\n- \n
- Public Methods: Public methods can be called by any object. By default, methods are public, except for the initialize method, which is always private. \n
- Private Methods: Private methods cannot be accessed or viewed from outside the class. Only class methods can access private members. \n
- Protected Methods: Protected methods can only be called by objects of the class and its subclasses. Access is also restricted to within the class and its subclasses. \n
Here is a simple example demonstrating the syntax of these three modifiers:
\n\nExample
\n\nclass Box\n def initialize(w,h)\n @width, @height = w, h\n end\n def getArea\n getWidth() * getHeight\n end\n def getWidth\n @width\n end\n def getHeight\n @height\n end\n private :getWidth, :getHeight\n def printArea\n @area = getWidth() * getHeight\n puts "Big box area is : #@area"\n end\n protected :printArea\nend\n\nbox = Box.new(10, 20)\na = box.getArea()\nputs "Area of the box is : #{a}"\nbox.printArea()\n\n\n\nWhen the above code is executed, it produces the following result. Here, the first method call succeeds, but the second method call causes an issue.
\n\nArea of the box is : 200\ntest.rb:42: protected method `printArea' called for #<Box:0xb7f11280 @height=20, @width=10> (NoMethodError)\n\nInheritance is one of the most important concepts in object-oriented programming. Inheritance allows us to define a class based on another class, making it easier to create and maintain applications.
\n\nInheritance helps with code reuse and faster execution. Unfortunately, Ruby does not support multiple inheritance, but Ruby supports mixins. A mixin is like a specific implementation of multiple inheritance where only the interface part is inheritable.
\n\nWhen creating a class, the programmer can directly specify that the new class inherits members from an existing class, eliminating the need to write new data members and member functions from scratch. This existing class is called the base class or parent class, and the new class is called the derived class or subclass.
\n\nRuby also provides the concept of subclassing, which is inheritance. The following example illustrates this concept. The syntax for extending a class is very simple. Just add a < character and the parent class name to the class statement. For example, the following defines class BigBox as a subclass of Box:
\n\nExample
\n\nclass Box\n def initialize(w,h)\n @width, @height = w, h\n end\n def getArea\n @width * @height\n end\nend\n\nclass BigBox < Box\n def printArea\n @area = @width * @height\n puts "Big box area is : #@area"\n end\nend\n\nbox = BigBox.new(10, 20)\nbox.printArea()\n\n\n\nWhen the above code is executed, it produces the following result:
\n\nBig box area is : 200\n\nAlthough you can add new functionality in a derived class, sometimes you might want to change the behavior of a method already defined in the parent class. In this case, you can keep the method name the same and override its functionality, as shown in the following example:
\n\nExample
\n\nclass Box\n def initialize(w,h)\n @width, @height = w, h\n end\n def getArea\n @width * @height\n end\nend\n\nclass BigBox < Box\n def getArea\n @area = @width * @height\n puts "Big box area is : #@area"\n end\nend\n\nbox = BigBox.new(10, 20)\nbox.getArea()\n\n\n\nThe output of the above example is:
\n\nBig box area is : 200\n\nWe want to use the + operator to perform vector addition of two Box objects, use the * operator to multiply the width and height of a Box, and use the unary operator - to negate the width and height of a Box. Here is a version of the Box class with mathematical operator definitions:
\n\nclass Box\n def initialize(w,h)\n @width,@height = w, h\n end\n def +(other)\n Box.new(@width + other.width, @height + other.height)\n end\n def -@\n Box.new(-@width, -@height)\n end\n def *(scalar)\n Box.new(@width*scalar, @height*scalar)\n end\nend\n\nSometimes, we want to prevent objects from being modified. In Object, the freeze method can achieve this, effectively turning an object into a constant. Any object can be frozen by calling Object.freeze. A frozen object cannot be modified, meaning you cannot change its instance variables.
\n\nYou can use the Object.frozen? method to check if a given object has been frozen. If the object is frozen, the method returns true; otherwise, it returns a false value. The following example illustrates this concept:
\n\nExample
\n\nclass Box\n def initialize(w,h)\n @width, @height = w, h\n end\n def getWidth\n @width\n end\n def getHeight\n @height\n end\n def setWidth=(value)\n @width = value\n end\n def setHeight=(value)\n @height = value\n end\nend\n\nbox = Box.new(10, 20)\nbox.freeze\n\nif(box.frozen? )\n puts "Box object is frozen object"\nelse\n puts "Box object is normal object"\nend\n\nbox.setWidth = 30\nbox.setHeight = 50\n\nx = box.getWidth()\ny = box.getHeight()\n\nputs "Width of the box is : #{x}"\nputs "Height of the box is : #{y}"\n\n\n\nWhen the above code is executed, it produces the following result:
\n\nBox object is frozen object\ntest.rb:20:in `setWidth=': can't modify frozen object (TypeError)\n from test.rb:39\n\nYou can define a constant inside a class by assigning a literal value or string to a variable. Constants do not require @ or @@ for their definition. By convention, constant names are written in uppercase.
\n\nOnce a constant is defined, you cannot change its value. You can access constants directly inside the class as if accessing variables, but if you want to access constants from outside the class, you must use classname::constant, as shown in the following example.
\n\nExample
\n\nclass Box\n BOX_COMPANY = "TATA Inc"\n BOXWEIGHT = 10\n def initialize(w,h)\n @width, @height = w, h\n end\n def getArea\n @width * @height\n end\nend\n\nbox = Box.new(10, 20)\na = box.getArea()\nputs "Area of the box is : #{a}"\nputs Box::BOX_COMPANY\nputs "Box weight is: #{Box::BOXWEIGHT}"\n\n\n\nWhen the above code is executed, it produces the following result:
\n\nArea of the box is : 200\nTATA Inc\nBox weight is: 10\n\nClass constants can be inherited and overridden like instance methods.
\n\nThere might be a situation where you want to create an object without calling the object constructor initialize, that is, using the new method to create an object. In this case, you can call allocate to create an uninitialized object, as shown in the following example:
\n\nExample
\n\nclass Box\n attr_accessor :width, :height\n def initialize(w,h)\n @width, @height = w, h\n end\n def getArea\n @width * @height\n end\nend\n\nbox1 = Box.new(10, 20)\nbox2 = Box.allocate\n\na = box1.getArea()\nputs "Area of the box is : #{a}"\na = box2.getArea()\nputs "Area of the box is : #{a}"\n\n\n\nWhen the above code is executed, it produces the following result:
\n\nArea of the box is : 200\ntest.rb:14: warning: instance variable @width not initialized\ntest.rb:14: warning: instance variable @height not initialized\ntest.rb:14:in `getArea': undefined method `*' for nil:NilClass (NoMethodError)\n from test.rb:29\n\nRuby's self is similar to Java's this, but quite different. In Java, methods are referenced within instance methods, so this generally points to the current object. In Ruby, code is executed line by line, so self has different meanings in different contexts. Let's look at the following example:
\n\nExample
\n\nclass Box\n puts "Class of self = #{self.class}"\n puts "Name of self = #{self.name}"\nend\n\n\n\nWhen the above code is executed, it produces the following result:
\n\nClass of self = ClassName\nName of self = Box\n\nThis means that class definitions can be executed with the class as the current object, and it also means that methods in the metaclass and parent class are available during the execution of method definitions.
YouTip