CSC/ECE 517 Summer 2008/wiki1 8 smr
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:
- 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.
- 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.
- 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 employees 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. Because in class-based programming we are creating classes in advance to represent multiple objects, we are less inclined to create separate classes to represent a subtle difference between objects. Consider the exercise of ABC Trucks wanting to provide different benefits to the employees in NY and those employees 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 than it may end up existing in multiple places. In our prototype-based solution however, we could maintain a hash table that has the subtly different objects that we can use as prototypes for creating new objects. |
The last section within 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 than 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
- Prototype Based Programming Makes A Comeback!
- Software Maintenance and Prototype Based Languages
- Taw's blog: Prototype-based Ruby
- Programming Ruby: The Pragmatic Programmers' Guide - Extending Objects