CSC/ECE 517 Fall 2007/wiki1 8 s1

From Expertiza_Wiki
Revision as of 20:15, 12 September 2007 by Mbsimmon (talk | contribs)
Jump to navigation Jump to search

The extend method in Ruby is designed to mix in methods of a module to a class at the class level. The term ‘class level’ is synonymous with the concept of static in other popular object oriented languages such as Java or C# where static behaviors and attributes exist independently of any instance of the class.

Module

Before presenting an example of the extend method, it is necessary to briefly define modules and introduce a small example.


A module is a collection of functionality that cannot be used to create objects, which is to say that alone it does not define a type. It exists to group functionality that will be used by objects either at the implementation level or the class level, depending on the mechanism by which it was added.


Below is an example of a module in Ruby. Its purpose is to group benefit calculation functionality for procedures performed at a general treatment facility. It has been simplified for illustration purposes – in practice benefit calculation is immensely complicated.


# module for encapsulating benefit calculation functionality
module BenefitCalculation
def getPatientCharge
puts “getPatientCharge from BenefitCalculation”
end


def getCopay
puts “getCopay from BenefitCalculation”
end
end


Extend Example

The most effective way to explain the use of extending a module is through an example, so consider a full-service treatment center (like a hospital) that provides medical, dental, optical and homeopathic services. A software solution developed to support this type of organization may consist of four modules to encapsulate the functionality specific to their services, but there would also be functionality common to all services in a base class.

A possible high-level design of the software architecture follows:


# Service is the base class for our system.
class Service
def getAddress
puts “getAddress from Service”
end
end


class DentalService < Service
# added functionality for dental services
end


class MedicalService < Service
# added functionality for medical services
end


class OpticalService < Service
# added functionality for optical services
end


class HomeopathicService < Service
# added functionality for homeopathic services
end


Now consider that there are some services for which insurance is an option, and there are others for which it is not. In our example, let’s consider dental and medical services to be the only services appropriate for insurance support. Where would be a good place to encode the insurance benefit calculation?


Option One


We could place it in the specific classes that accept insurance support (DentalService and MedicalService), but in doing so we would be duplicating code. Code duplication becomes a maintenance nightmare, and the difficulty grows rapidly with the number of times the code is duplicated. For this reason, it would probably be a bad idea to place the needed code directly into the class definitions of DentalService and MedicalService.


Option Two


A another option would be to define the functionality in the base class Service. This would allow us to maintain one copy of the code so that when benefit calculation changes (as it does almost weekly), changes could be made in one place and thus maintenance time is minimized. The problem with this solution is that OpticalService and HomeopathicService also inherit the functionality. Because services of their type do not support insurance, the functionality is inappropriate for their type definitions.


Option Three - Extend


Ruby provides a good solution to this problem. Using the extend method, we can place the needed benefit calculation functionality inside of a module and extend that module where it is appropriate. With this solution we solve the problems from options one and two illustrated above: 1) there is a single copy of the functionality in our solution and 2) the functionality is only added to the types for which it is appropriate.

Modifying our hierarchy above to fit this solution, we end up with the following:


class DentalService < Service
# added functionality for dental services
extend BenefitCalculation
end


class MedicalService < Service
# added functionality for medical services
extend BenefitCalculation
end


Now that we have a clean design, let's execute some code in the IRB and see the results. Assume the module and all class definitions have been loaded.


irb> medical = MedicalService.new
irb> optical = OpticalService.new
irb> optical.getAddress
# yields 'getAddress from Service'
irb> OpticalService.getPatientCharge
# yields NoMethodError (we didn’t extend)
irb> OpticalService.getCopay
# yields NoMethodError (we didn’t extend)
irb> DentalService.getPatientCharge
# yields 'getPatientCharge from BenefitCalculation'
irb> DentalService.getCopay
# yields 'getCopay from BenefitCalculation'


Caution

Again take note that the use of extend lets us call module functionality at the class level. Were we to attempt to call extended functionality at the object level we would get a NoMethodError.

irb> medical.getCopay
# yields NoMethodError

If you need to add module functionality at the object level, use include instead of extend. Consider the following new definition of the MedicalService class:


class MedicalService < Service
# added functionality for medical services
include BenefitCalculation
end


Observe the behavior of the following calls executed in the IRB and the results. Compare the output listed here with the output above.

irb> medical = MedicalService.new
irb> medical.getPatientCharge
# yields 'getPatientCharge from BenefitCalculation'
irb> medical.getCopay
# yields 'getCopay from BenefitCalculation'
irb> MedicalService.getPatientCharge
# yields NoMethodError (we used include)
irb> MedicalService.getCopay
# yields NoMethodError (us used include)