CSC/ECE 517 Fall 2007/wiki1 8 s1: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
mNo edit summary
m (Added dynamic extention of modules by instantiated objects.)
 
(7 intermediate revisions by the same user not shown)
Line 1: Line 1:
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.
This page is a response to the following assignment:


==Module==
<blockquote>
''Devise one or more practical examples of Ruby's extend method--that is, examples that serve a real need and are not contrived. (If you can find existing examples on the Web, critique their practicality.) For inspiration, you might want to read up on prototype-based languages.''
</blockquote>
 
The objective is to educate through example the proper use of Ruby's '''extend''' method and in doing so briefly introduce modules and the '''include''' method to illustrate how it differs from '''extend'''.  The design described in this solution is based on a software system implemented by the author.  As the assignment asks for originality of example, external links are minimized.  However, other examples are referenced at the end of the document.
 
 
<center>----------</center>
 
== Introduction ==
 
The '''extend''' method in Ruby is designed to mix in methods of a '''module''' to a class definition at the class level or to an instantiated object dynamically.  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.
Before presenting an example of the '''extend''' method, it is necessary to briefly define modules and introduce a small example.
Line 11: Line 25:
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.
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.


<pre>
    # module for encapsulating benefit calculation functionality
    module BenefitCalculation
          # call to get the appropriate charge for a procedure
          # assume the appropriate arguments are sent in
          def getPatientCharge
              puts “getPatientCharge from BenefitCalculation”
          end
          # call to get the appropriate copay for a given procedure and insurance type
          # assume the appropriate arguments are sent in
          def getCopay
              puts “getCopay from BenefitCalculation”
          end


:<nowiki># module for encapsulating benefit calculation functionality</nowiki>
    end
:'''module''' BenefitCalculation
</pre>


::'''def''' getPatientCharge
== Extend Example ==
:::'''puts''' “getPatientCharge from BenefitCalculation”
::'''end'''


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.


::'''def''' getCopay
A possible high-level design of the software architecture follows:
:::'''puts''' “getCopay from BenefitCalculation”
::'''end'''


:'''end'''
<pre>
    # Service is the base class for our system.
    class Service


          # call to get the address of the service center
          def getAddress
              puts “getAddress from Service”
          end


==Extend Example==
    end


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 surely there would be functionality that would be common to all services in a base class.


A possible design of the high-level software architecture follows:
    class DentalService < Service
          # added functionality for dental services
    end




:<nowiki># Service is the base class for our system.</nowiki>
    class MedicalService < Service
:'''class''' Service
          # added functionality for medical services
    end


::'''def''' getAddress
:::'''puts''' “getAddress from Service”
::'''end'''


:'''end'''
    class OpticalService < Service
          # added functionality for optical services
    end




:'''class''' DentalService < Service
    class HomeopathicService < Service
::<nowiki># added functionality for dental services</nowiki>
          # added functionality for homeopathic services
: '''end'''
    end
</pre>




:'''class''' MedicalService < Service
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?''
::<nowiki># added functionality for medical services</nowiki>
:'''end'''




:'''class''' OpticalService < Service
'''''Option One'''''
::<nowiki># added functionality for optical services</nowiki>
:'''end'''




:'''class''' HomeopathicService < Service
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.
::<nowiki># added functionality for homeopathic services</nowiki>
:'''end'''




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


'''''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 codeCode duplication becomes a maintenance nightmare, and the difficulty grows rapidly with the number of times code is duplicatedFor this reason, it would probably be a bad idea to place the needed code directly into the class definitions of DentalService and MedicalService.
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 minimizedThe problem with this solution is that OpticalService and HomeopathicService also inherit the functionalityBecause services of their type do not support insurance, the functionality is inappropriate for their type definitions.


'''''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'''''


'''''Option Three'' - Extend'''


Ruby provides a 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 we solve problems 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.
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:
Modifying our hierarchy above to fit this solution, we end up with the following:


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


:'''class''' DentalService < Service
::<nowiki># added functionality for dental services</nowiki>
::'''extend''' BenefitCalculation
:'''end'''


    class MedicalService < Service
          # added functionality for medical services


:'''class''' MedicalService < Service
          extend BenefitCalculation
::<nowiki># added functionality for medical services</nowiki>
    end
::'''extend''' BenefitCalculation
</pre>
:'''end'''




Now that we have our design requirements fulfilled, we can execute the code in the IRB and see what we get.  Assume the module and all class definitions have been loaded.
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'''>  medical = MedicalService.new


:::irb>  optical = OpticalService.new
:::'''irb'''>  optical = OpticalService.new


:::irb>  optical.getAddress
:::'''irb'''>  optical.getAddress
:::::<nowiki># yields 'getAddress from Service'</nowiki>
:::::<nowiki># yields 'getAddress from Service'</nowiki>


:::irb>  OpticalService.getPatientCharge
:::'''irb'''>  OpticalService.getPatientCharge
:::::<nowiki># yields NoMethodError (we didn’t extend)</nowiki>
:::::<nowiki># yields NoMethodError (we didn’t extend)</nowiki>


:::irb>  OpticalService.getCopay
:::'''irb'''>  OpticalService.getCopay
:::::<nowiki># yields NoMethodError (we didn’t extend)</nowiki>
:::::<nowiki># yields NoMethodError (we didn’t extend)</nowiki>


:::irb>  DentalService.getPatientCharge
:::'''irb'''>  DentalService.getPatientCharge
:::::<nowiki># yields 'getPatientCharge from BenefitCalculation'</nowiki>
:::::<nowiki># yields 'getPatientCharge from BenefitCalculation'</nowiki>


:::irb>  DentalService.getCopay
:::'''irb'''>  DentalService.getCopay
:::::<nowiki># yields 'getCopay from BenefitCalculation'</nowiki>
:::::<nowiki># yields 'getCopay from BenefitCalculation'</nowiki>




==Caution==
Another option would be to use extend to add the functionality of the module dynamically to individual objects instead of statically to the class definitions.  When doing this, the functionality can be called at the individual object level.  If we return to our original definition of MedicalService (with no module extended in the class definition), we can add the module functionality to the instantiated object dynamically as follows:
 
:::'''irb'''>  medical = MedicalService.new


:::'''irb'''>  medical.extend(BenefitCalculation)


Again take note that the use of extend let us call module functionality at the '''class level'''. Were we to try to attempt to call extended functionality at the object level we would get a NoMethodError.
:::'''irb'''> medical.getPatientCharge
:::::<nowiki># yields 'getPatientCharge from BenefitCalculation</nowiki>


::::irb>  medical.getCopay
 
The software system used in this example is being developed using C# on the Microsoft .NET platform, and modules like those in Ruby are unavailable.  In order to get around the problems illustrated in options one and two above, BenefitCalculation functionality is itself implemented in a class containing static methods, and in order to perform the appropriate calculations the corresponding BenefitCalculation method is called and sent the appropriate arguments, as illustrated below:
 
<pre>
    public class Service
    {
          public virtual float getPatientCharge()
          {
              return float.MinValue;    // indicates nothing was calculated
          }
    }
 
    public class MedicalService : Service
    {
          public override float getPatientCharge()
          {
              return BenefitCalculation.getPatientCharge();
          }
    }
</pre>
 
Notice that there is only one copy of the benefit calculation code in the C# solution, so the problem presented by option one is avoided.  Likewise, HomeopathicService simply doesn't override getPatientCharge(), so the inappropriate inheritance of functionality illustrated in option two is also avoided.  The difference is that objects of type MedicalService no longer contains the functionality for calculating the patient charge, but can get to it.  Static languages by nature don't allow the dynamic addition of functionality as illustrated above, so that was not an option for this project.
 
 
== Caution ==
 
 
Again take note that the use of extend lets us call module functionality at the ''class level'' if used in the context of the class definition.  In this same context, were we to attempt to call extended functionality at the object level we would get a NoMethodError.
 
::::'''irb'''>  medical.getCopay
:::::<nowiki># yields NoMethodError</nowiki>
:::::<nowiki># yields NoMethodError</nowiki>


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:
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:


<pre>
    class MedicalService < Service
          # added functionality for medical services


:'''class''' MedicalService < Service
          include BenefitCalculation
::<nowiki># added functionality for medical services</nowiki>
    end
::'''include''' BenefitCalculation
</pre>
:'''end'''


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


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
::::'''irb'''>  medical.getPatientCharge
:::::<nowiki># yields 'getPatientCharge from BenefitCalculation'</nowiki>
:::::<nowiki># yields 'getPatientCharge from BenefitCalculation'</nowiki>


::::irb>  medical.getCopay
::::'''irb'''>  medical.getCopay
:::::<nowiki># yields 'getCopay from BenefitCalculation'</nowiki>
:::::<nowiki># yields 'getCopay from BenefitCalculation'</nowiki>


::::irb>  MedicalService.getPatientCharge
::::'''irb'''>  MedicalService.getPatientCharge
:::::<nowiki># yields NoMethodError (we used include)</nowiki>
 
::::'''irb'''>  MedicalService.getCopay
:::::<nowiki># yields NoMethodError (we used include)</nowiki>
:::::<nowiki># yields NoMethodError (we used include)</nowiki>


::::irb>  MedicalService.getCopay
 
:::::<nowiki># yields NoMethodError (us used include)</nowiki>
== References ==
 
A [http://www.juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby/ Ruby module tutorial] illustrating of the uses of '''extend''' and '''include'''.
 
Another [http://www.rabbitcreative.com/2007/07/27/create-class-level-methods-when-including-a-module-in-ruby/ Ruby module tutorial] again illustrating the uses of '''extend''' and '''include'''.

Latest revision as of 02:33, 22 September 2007

This page is a response to the following assignment:

Devise one or more practical examples of Ruby's extend method--that is, examples that serve a real need and are not contrived. (If you can find existing examples on the Web, critique their practicality.) For inspiration, you might want to read up on prototype-based languages.

The objective is to educate through example the proper use of Ruby's extend method and in doing so briefly introduce modules and the include method to illustrate how it differs from extend. The design described in this solution is based on a software system implemented by the author. As the assignment asks for originality of example, external links are minimized. However, other examples are referenced at the end of the document.


----------

Introduction

The extend method in Ruby is designed to mix in methods of a module to a class definition at the class level or to an instantiated object dynamically. 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

          # call to get the appropriate charge for a procedure
          # assume the appropriate arguments are sent in
          def getPatientCharge 
               puts “getPatientCharge from BenefitCalculation”
          end

          # call to get the appropriate copay for a given procedure and insurance type
          # assume the appropriate arguments are sent in
          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

          # call to get the address of the service center
          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


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'


Another option would be to use extend to add the functionality of the module dynamically to individual objects instead of statically to the class definitions. When doing this, the functionality can be called at the individual object level. If we return to our original definition of MedicalService (with no module extended in the class definition), we can add the module functionality to the instantiated object dynamically as follows:

irb> medical = MedicalService.new
irb> medical.extend(BenefitCalculation)
irb> medical.getPatientCharge
# yields 'getPatientCharge from BenefitCalculation


The software system used in this example is being developed using C# on the Microsoft .NET platform, and modules like those in Ruby are unavailable. In order to get around the problems illustrated in options one and two above, BenefitCalculation functionality is itself implemented in a class containing static methods, and in order to perform the appropriate calculations the corresponding BenefitCalculation method is called and sent the appropriate arguments, as illustrated below:

     public class Service 
     {
          public virtual float getPatientCharge() 
          {
               return float.MinValue;     // indicates nothing was calculated
          }
     }

     public class MedicalService : Service
     {
          public override float getPatientCharge()
          {
               return BenefitCalculation.getPatientCharge();
          }
     }

Notice that there is only one copy of the benefit calculation code in the C# solution, so the problem presented by option one is avoided. Likewise, HomeopathicService simply doesn't override getPatientCharge(), so the inappropriate inheritance of functionality illustrated in option two is also avoided. The difference is that objects of type MedicalService no longer contains the functionality for calculating the patient charge, but can get to it. Static languages by nature don't allow the dynamic addition of functionality as illustrated above, so that was not an option for this project.


Caution

Again take note that the use of extend lets us call module functionality at the class level if used in the context of the class definition. In this same context, 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 (we used include)


References

A Ruby module tutorial illustrating of the uses of extend and include.

Another Ruby module tutorial again illustrating the uses of extend and include.