Ruby II Classes Classes are straight forward in Ruby. expr Note that the class name begins with a capitol letter. Classes can contain instance variables, instance methods, class variables, class methods, and special accessor functions. Classes Instance Attributes/Methods Instance attributes and methods are variables and methods that belong to the object. Instance methods are simply methods defined inside of a class structure. Instance attributes are variables whose name begins with an @ that are used in an instance method. It is not possible to declare instance attributes outside of the instance methods. If an instance attribute is accessed before it is set, the value is nil. 1
Classes Instance Attributes/Methods def initialize() @balance = 0 def depositfunds(amount) @balance += amount myaccount = BankAccount.new() myaccount.depositfunds(100) In this example, the class is BankAccount, depositfunds is an instance method of BankAccount, and @balance is an instance attribute of BankAccount. We call the method after we create an object of type BankAccount. Classes Initialize Method You must create an object named initialize for each class you create. When you create a new object, you call the new method for the class. myaccount = BankAccount.new(500); The new method will create the object and then call the user defined initialize method in the class. Any parameters passed to new will be passed to the initialize method. Because you do not declare instance attributes ahead of time in Ruby, the initialize method is a good place to set up all instance attributes. Classes Initialize Cont. def initialize(startingbalance, accounttype= Checking, accountnumber=nil) @balance = startingbalance @accounttype = accounttype @accountnumber = accountnumber Initialize can only be declared once per class. If you wish to have optional parameters, you should set the default values in the parameter list. 2
Classes - Self An instance method is able to call another instance method. If you do not specify an object to be the receiver, it is assumed that the current object is the receiver. def transfermoney(receivingaccount, amount) withdrawfunds(amount) receivingaccount.depositfunds(amount) In this case, we did not specify a receiver for withdrawfunds, so the current object was passed. To make the code more clear, we could have used the self keyword. self simply refers to the current object. self.withdrawfunds(amount) Classes Access Control Instance variables are always private. You must use a method to allow get/set privileges from outside of the class. All instance methods besides initialize are public by default. initialize is always private. Though in some ways not really since new invokes it implicitly It is possible to change the access level of instance methods by specifying public, protected, or private. public: can be called by anyone private: can only be called by the current object. This means that you can not specify a receiver. protected: can by called in the class/subclass. Differs from private because you can specify a reciever. Classes Access Control cont. There are two ways to specify accessibility. #1) Write the accessibility level followed by a newline. Any subsequent methods will have that level. In the following definition, withdrawfunds is protected and the other methods are public. protected def withdrawfunds(amount) @balance -= amount public def depositfunds(amount) @balance += amount def transfermoney(receivingaccount, amount) receivingaccount.depositfunds(amount) withdrawfunds(amount) 3
Classes Access Control cont. #2) Declare all methods and then call the access levels with parameters passed to them. The parameter should be : + methodname. Any method name specified will have that access level. def withdrawfunds(amount) @balance -= amount def depositfunds(amount) @balance += amount def transfermoney(receivingaccount, amount) receivingaccount.depositfunds(amount) withdrawfunds(amount) protected :withdrawfunds, :depositfunds public :transfermoney Classes Attribute Accessors As mentioned before, instance attributes are always private. You can not specify public or protected for them. You must create get/set methods for the attributes if you want them to be accessed outside of the class. def balance return @balance def setbalance(amount) @balance = amount This could get tedious, so Ruby has made it much simpler to create these get/set methods with accessors. Classes Attribute Accessors Ruby offers three accessor methods to do the basic get/set for you. attr_reader: Implements the get behavior. attr_writer: Implements the set behavior. attr_accessor: Implements both get & set. All of these are used by specifying the proper method and then passing it a colon and the instance attribute s name. You can specify multiple attributes by separating the names with a comma. attr_reader :balance attr_writer :balance, :accounttype attr_accessor :balance Once you set these accessor methods, you can access the attribute just as you would if it were public. myaccount.balance = 100 puts myaccount.balance 4
Classes Virtual Attributes It is possible to allow the user to assign to an attribute that does not exist. myaccount.interestratepercent = 10 Then in the class, we can handle the incoming value and set interestrate appropriately. Note the equal sign at the of the method name. def interestratepercent= (percentage) @interestrate = (percentage.to_f/100.to_f).to_f Classes Class Variable A class variable is shared among all the instances of a class. It is useful if you want to keep a balance/count of all instances. A class variable MUST be initialized in the class outside of any methods. A class variable is specified by using @@name. The class variable is accessible in the instance methods and the class methods. @@allaccountbalance = 0 def withdrawfunds(amount) @balance -= amount @@allacountbalance -= amount Classes Class Methods A class method is a method for the class instead of the instance. A class method has no access to the instance attributes except through an object of the class. The class method is defined inside of the class. It apps the class name to it s definition def BankAccount.transferFunds(fromAccount, toaccount, amount) fromaccount.withdrawfunds(amount) toaccount.depositfunds(amount) Note that withdrawfunds and depositfunds must be public in order for this to work. 5
Classes Singleton Classes We can use class methods to create a singleton class in Ruby. In this example, we will limit the creation of Myself to a single object. We first disable the new method. We can do this by setting it to private. This way, we can control when new gets called and therefore how many objects are created of our class. It is possible to set an existing item to private by using the private_class_method method. We pass the method a colon followed by the name of the method we want to set to be private (in this case, new ) class Myself private_class_method :new Classes Singleton Classes cont. Next we create a new constructor (make) for the user to call. Our new constructor will decide to call new or not. We use a class method for our constructor and a class variable to store the actual object. If the object is not created yet, we can create it by calling new. Otherwise, we can just return the already existing object. class Myself private_class_method :new @@me = nil def Myself.make @@me = new unless @@me @@me thomas = Myself.make() print thomas.inspect() thomasclone = Myself.make() print thomasclone.inspect() Classes Inspect & to_s Two useful methods for testing are inspect and to_s. inspect lists the object s instance variables. myaccount.inspect() #<BankAccount:0x27b72f8 @accounttype="checking", @balance=500, @accountnumber=4654> to_s converts the object to a string. myaccount.to_s() #<BankAccount:0x27b72f8> We can overwrite to_s to present a more readable string. def to_s "Balance for #{@accounttype} Account ##{@accountnumber}: $#{@balance}" Balance for Checking Account #4654: $500 6
Classes - Inheritance When an object oriented language is implementing inheritance, it must decide if it will allow single inheritance, multiple inheritance, or some sort of hybrid. In life, multiple inheritance makes sense. Single inheritance limits object oriented programming. However, multiple inheritance can get very messy. There can be ambiguous attributes and it is difficult to read and maintain. Ruby implements single inheritance. An object can only have one parent. However, it creates a hybrid by allowing mixins to include additional functionality We will study mixins when we look at modules. Classes - Inheritance When declaring a class with a parent, we simply add < parentname to the of the class declaration class CheckingAccount < BankAccount expr A child class will inherit all of the parent s attributes and methods no matter what the access level. It is possible for a child class to overwrite any of the parent s methods. class CheckingAccount < BankAccount attr_accessor :pin def to_s PIN for Account ##{@accountnumber}: #{@pin}" Classes - Inheritance Notice that we did not create a new initialize method for CheckingAccount. When we call CheckingAccount.new(), Ruby will first see if CheckingAccount has an initialize function. When it does not find one, it will check the parent and see if the parent has one. This is how it behaves with all methods. Note there is obviously a top Object class just like in JavaScript that is the ultimate super class for everything If wanted to add additional fields to our initialization, we could create our own initialize. class CheckingAccount < BankAccount def initialize(startingbalance, pin, accountnumber=nil) @balance = startingbalance @pin = pin @accounttype = Checking @accountnumber = accountnumber 7
Classes - Super Instead of repeating the initialize code we use in BankAccount, we should just pass the appropriate parameters to the BankAccount initialize. We use the keyword super to access the parent methods. class CheckingAccount < BankAccount def initialize(startingbalance, pin, accountnumber=nil) super(startingbalance, Checking, accountnumber) @pin = pin Modules A module is a collection of classes, methods, and constants. Since modules can be stored in separate files with their own namespace and included (and mixed in) when needed, they are ideal for reusability. module Bank expr This module creates a namespace named Bank with our BankAccount class in it. Modules cont. There are various ways to access the objects in the module. You put constants and classes in the module the same way you would put it in a normal file. module Logs LOGPATH = /logs class LogFile In order to reference a constant or a class inside of a module, you must use the scope resolution operator (::) path = Logs::LOGPATH newfile = Logs::LogFile.new() 8
Modules cont. Methods in modules are private by default. In order to make them public, you must name the method like you would a class method: ModuleName.methodname module Logs def Logs.addLog(logmessage) expr Then you can call the method simply by calling the same name Logs.addLog( Success ) Modules - Mixins As mentioned in the classes section, we use modules to get around the need for multiple inheritance. As we saw in the last section, we had to declare methods with the module name in front of it in order to access it outside of the module. If we declare methods without the module name module Logs def addlog(logmessage) expr Logs.addLog( Success ) We get the following error: undefined method `addlog' for Logs:Module Modules - Mixins In this case, addlog is being implemented as an instance method and because a module is not a class, it can not use an instance method. However, we can mix-in the module with a class and then the class will have access to the instance method. include Logs Now, the BankAccount class can use addlog as if it were an instance method in BankAccount. myaccount = BankAccount.new myaccount.addlog( success ) If multiple mixins contain the same method name, the last one to be included is the method that will be used. 9
Modules Mixins cont. Mixin methods can call other methods in the class. Of course, the class must ensure that it defines any methods that the included mixin calls. module Logs def addlog(logmessage) puts self.getaccount + : + logmessage include Logs def getaccount return #{@accounttype} Account ##{@accountnumber} myaccount = BankAccount.new myaccount.addlog( success ) Modules Mixins cont. Mixins can access/set instance attributes in the same manner as methods. module Logs attr_reader :logsadded def addlog(logmessage) @logsadded = true include Logs myaccount = BankAccount.new myaccount.addlog( success ) puts myaccount.logsadded Note: The same possible conflict does exist if two included mixins contain the same attribute name. Hmmm you kind of wonder if this really doesn t have the same types of problems of multiple inheritance 10