CSC/ECE 517 Summer 2008/wiki1 8 smr

From Expertiza_Wiki
Jump to navigation Jump to search

In Lecture 7, we consider prototype-based programming in Ruby. The example that is given is somehwhat artificial. Can you identify real tasks that are easier with prototype-based programming than with class-based programming? Give the code, at least in outline


Introduction

Many of the object-oriented systems created over the years have been done so using class-based programming. In class-based programming we define structures during implementation that describe the properties and operations of objects to be created at runtime. During runtime, a program creates an instance of an object by referencing the class that defines the desired structure and behavior for that object. While each instance contains its own copy of data, the behavioral aspects of a class are common between all instances and are not typically altered during the execution of the program. Growing in popularity is another form of object-oriented programming known as prototype-based programming[1].

In prototype-based programming, instead of referencing a pre-defined class to create an object, an object is created by making a copy of an existing object. Typically, prototype-based programming follows these steps:

  1. Start with a Prototype: There is at least one concrete object in the system and it serves as the catalyst for all subsequently created objects. This object as well as any other objects that exist serve as prototypes for creating new objects.
  2. Clone the Prototype: A new object is created by cloning an existing prototype. The newly created object inherits all of the structural and behavioral aspects of the prototype.
  3. Tailor or Extend the Object: The newly created object is then tailored for its specific purposes and its possible the object is enhanced to provide new functionality.

Prototype-based programming continues to follow these steps, to create the host of objects that exist within the runtime environment.

Prototype-based programming has been said to be more powerful than class-based programming [2], which is something we explore in this article. Below we use an example management information system to compare alternative approaches offered by class-based and prototype-based programming. This example is intended to highlight tasks made easier by using prototype-based programming.

Example

Problem

Most organizations use some type of employee management system to store and modify a variety of information about their employees. In this example, ABC Trucks needs a new system that allows them to support the needs of all their employees. ABC trucks classifies their employees as Full-time, Manager, Temporary, and/or Remote. Each classification represents different information to be stored for an employee as well as added functionality need to manage that type of employee. For the purposes of our example, the classifications are not mutually exclusive so an employee might qualify for zero or more classifications. The objective is to create an extensible employee management system that supports all of the ABC Trucks employees.

Class-based Approach

In a class-based approach, we might create classes to represent all of the possible employee classifications, while using multiple-inheritance to share common functionality between the classes. For this example, we would need to define 16 (24) different classes, each with different properties and operations.

These classes are certainly representative of the employees that work at ABC Trucks. However, this approach requires quite a bit of code to be developed, tested, and maintained.

We’re now going to discuss a prototype-based programming approach to this same problem. Throughout the discussion of the prototype-based approach, we will refer back to this class-based approach in order to draw comparisons and to highlight tasks made easier by prototype-based programming.

Prototype-based Approach

We’ve selected Ruby as our target language, which provides us the option to use prototype-based programming. For our prototype-based approach, rather than creating a class for every possible type of employee, we’ll start with a single Employee object. This object will then be cloned and extended to support all of the employee classifications.

The code below defines a base employee object that has the common properties and operations of all employees. In addition, we define individual modules to represent each of the employee classifications.

 # initial employee object
 class Employee
   include Comparable
   
   attr_reader :name, :ssn
   attr_writer :name, :ssn
   
   def initialize(ssn, name)
     @name, @ssn = name, ssn
   end
   
   def key
     ssn
   end
   
   def <=>(other)
     self.name <=> other.name
   end
   
   def to_s()	
     self.name
   end
   
   # dump all initialized variables of this object
   def dump
     s = ""
     
     self.instance_variables.each do |v| 
       s += v.gsub(/^@(.+)/, "\\1: #{eval(v)}\n") 
     end 
     
     return s
   end
 end
 
 # provides manager properties and operations
 module Manager
   def direct_reports
     @direct_reports ||= []
   end
 end
 
 # provides full-time employee properties and operations
 module FullTime
   attr_reader :salary, :benefits, :vacation_bal
   attr_writer :salary, :benefits, :vacation_bal
   
   def recordVacation(date)
     date
   end
 end
 
 # provides temp-employee properties and operations
 module Temp
   attr_reader :location, :agency
   attr_writer :location, :agency
 end
 
 # provides remote employee properties and operations
 module Remote
   attr_reader :location, :dial_in_number
   attr_writer :location, :dial_in_number
 end
 

In contrast to the definition of the 16 classes needed for the class-based solution, we are able to get away with defining only 5 different classes (or modules).

Prototype-based Programming Advantage: Object-oriented systems with objects requiring numerous classifications can benefit by using prototype-based programming.

If n is the number of classifications for a particular type of object, then class-based programming requires an upper bound of 2n class definitions to allow us to represent all possible objects. Alternatively, prototype-based programming requires an upper bound of only n class definitions (or modules) to allow us to represent all possible objects.

To drive this point home, consider if our requirements were later changed to add 1 more employee classification. Our prototype-based solution would need the definition of 1 more module, whereas the class-based solution might require the definition of 16 additional classes.

In prototype-based programming, the idea is to create subsequent objects based on an initial prototype. To facilitate this, we’ve created the singleton EmployeeSystem below, which provides a method for creating new employee objects based on an existing employee object.

 require "singleton"
 
 # EmployeeSystem is hub for creating employees
 # only 1 version of the system exists
 class EmployeeSystem < Hash
   include Singleton
   
   DEFAULT_EMPLOYEE = Employee.new("000000000", "")
   
   # add employee to the collection
   def saveEmployee(e)
     self[e.key] = e
   end
   
   # create employee object based on default 
   # or existing  employee prototype
   def newEmployee(key = nil)
     if (key)
       e = self[key]
     end
     
     if (!e)
       e = EmployeeSystem::DEFAULT_EMPLOYEE
     end
     
     return e.clone()
   end
   
   # dump out the employee information
   def employeeReport()
     puts "\nEmployee Report"
     	
     self.values.sort.each do |e|
     puts "============================================\n"
     puts "Name: #{e}\n\n-----General Information--------\n#{e.dump}\n" 
   end
   
   puts "============================================\n\n"
   
 end

The EmployeeSystem provides the method newEmployee(key), which returns a prototype employee object based on either an existing employee or a default employee object. Note that the returned prototype is a clone of the requested employee object.

We now create a new employee object using the newEmployee method.

 # retrieve the default employee object to use a prototype
 e1 = EmployeeSystem.instance.newEmployee()
 
 # initialize the fields of this new employee
 e1.name = "Tom Smith"
 e1.ssn = "123123123"
 
 # extend this particular employee to be a full-time employee
 e1.extend(FullTime)
 
 # initialize the fields specific to full-time employees
 e1.salary = 50_000
 e1.benefits = "Private plane on the weekends"
 
 # save this new employee back to the employee database
 EmployeeSystem.instance.saveEmployee(e1)

Above we’ve extended our default employee prototype to create a new type of full-time employee.

Prototype-based Programming Advantage: Using prototype-based programming, we are able to treat any object as a prototype and therefore we can extend the object without having to create a new object. In class-based programming, we are committed to the functionality provided by the object at instantiation. If we later need to extend the functionality of the class-based object, we must instantiate a new class-based object that has the desired functionality and transfer state between the two objects.

Consider the exercise to promote an employee from a full-time employee to a full-time manager. In our prototype-based solution, we simply extend the FullTime employee object to have the functionality of a Manager. In our class-based solution, we must instantiate a new FullTimeManager object and transfer state from the existing FullTime employee object.

At this point, we have two employee objects that can act as a prototype for a new employee. In this next step, we’ll create another employee object, but this time we’ll use the employee we just created as the prototype.

 # create a new employee based on the Tom Smith prototype
 e2 = EmployeeSystem.instance.newEmployee("123123123")
 
 # initialize the fields of this new employee
 e2.name = "Mia Marie"
 e2.ssn = "456456456"
 
 # initialize the fields specific to full-time employees
 # Note: we didn't need to extend this employee to be a full-time
 e2.salary = 100_000
 
 # extend this particular employee to be a Manager
 e2.extend(Manager)
 
 # initialize the fields specific to Managers
 # Tom Smith reports to Mia Marie
 e2.direct_reports << e1
 
 # save this new employee back to the employee database
 EmployeeSystem.instance.saveEmployee(e2)
 

In the above snippet of code, we created a new full-time employee based on the prototype of an existing full-time employee. Notice for this newest employee, we didn’t initialize the benefits field. This is because this information was already initialized within our prototype and is the desired value for our new employee.

Prototype-based Programming Advantage: Prototype-based programming simplifies our ability to provide default data and functionality for newly created objects. This simplification can reduce the amount of code needed to initialize new objects. We have similar functionality in class-based programming by setting default values, but not to the extent of prototype-based programming. In class-based programming we are creating classes in advance to represent multiple objects, so we are less inclined to create separate classes to represent subtle differences between objects.

Consider the exercise of ABC Trucks wanting to provide different benefits to the employees in NY than those in NC. We are not likely to create another type of employee sub-class for this subtle difference (doing so would require an additional 16 classes!). As a result, the initialization of the benefits field would have to occur after instantiation, and if that logic must be external to the class, then it might need to exist in multiple places. In our prototype-based solution however, we could maintain a hash table that has the subtly different objects, which we can then use as prototypes for creating new objects.

The last section in the above code, further extends the full-time employee to be a manager and assigns them a direct report. As mentioned prior, extending our full-time employee to now be a full-time manager is considerably easier then it would have been in our class-based solution.

To round out our example, we run the report for the Employee System so that we can review the internals of each of the objects.

 # display a report of our employees
 EmployeeSystem.instance.employeeReport()
 Employee Report
 ============================================
 Name: Mia Marie
 
 -----General Information-------
 benefits: Private plane on the weekends
 salary: 100000
 name: Mia Marie
 ssn: 456456456
 direct_reports: Tom Smith
 
 ============================================
 Name: Tom Smith
 
 -----General Information-------
 benefits: Private plane on the weekends
 salary: 50000
 name: Tom Smith
 ssn: 123123123
 
 ============================================

Conclusion

In this article we have provided a brief overview of prototype-based programming along with an example of its use in the real world. Our example highlighted some real tasks that are simpler using prototype-based programming when compared to class-based programming. Although class-based programming is still more widely used [3], we encourage readers to consider how a prototype-based approach may simplify some of their object-oriented programming tasks. For those instances where a prototype-based approach is more economical, it pays to have a prototype-based programming language available in the toolbox. Our recommendation is that readers consider having a language like Ruby in their toolbox, which will give them the best of both worlds.

Also See

References

  1. Wikipedia: Prototype-based programming
  2. Softpanoroma: Prototype Based Object-Oriented
  3. TIOBE Software: TIOBE Programming Community Index