Object oriented features of JScript .NET

This week's article is all about classes in JScript .NET. I introduced the class statement in article four of this series. This article goes into more detail covering class access rules, inheritance, and polymorphism. The article includes some sample code that demonstrates how to use the features I discuss in this article.

Understanding class access rules

Article four of this series introduced the class statement, member variables, constructors, and static members. While classes are a great way to encapsulate behavior and data into a single type, they're not useful if you cannot hide certain details from other code in your application. For example, a class that has an amount member would not be aware if some other code in the application directly modifies its value - the class could not respond to the change and would not be able to determine if the new value is appropriate. This is where class member attributes come in.

By default, all class members are visible to all code within an application. This means that any code can modify a class's member variables or call its member functions without restrictions - this is similar to regular JScript objects you create yourself.

When you create a new class, you can grant or deny access to the class's member variables and functions using member attributes. There are three types of member attributes:

  • public : This is the default attribute; public members are accessible from all code within an application
  • private : These types of members are accessible only within the class that declares them - all other code within an application cannot access private members.
  • protected : These types of members are accessible within the class that declares them and classes that derive from the class that declares them (I'll discuss the term 'derives' shortly).

You can change a class member's attributes as shown in the following listing:

class kitchenAppliance {

  private var type : int;

  protected var weight : int;

  // public is implicit - the following declarations are public
  var height: int;

  function kitchenAppliance()
  {
    //...
  }
}

The listing changes the visibility of the type member to private (meaning the variable is accessible only within the class that defines it), and the visibility of the weight member to protected (meaning that it is visible to the defining class and classes that derive from it). The height member variable and kitchenAppliance function (the class's constructor) are both publicly visible.

Understanding inheritance

Most people have more than one kitchen appliance: stove, fridge, and perhaps a dishwasher. One thing in common with each of these appliances is that they play a role in a typical kitchen: we use them to prepare or store food, or clean up when we're done. When you think of a stove, you implicitly understand that it is a kitchen appliance, as is a fridge and dishwasher. In effect, a stove is a type of appliance.

When you notice or discover an "is a" relationship between two objects, they are said to share some characteristic that establishes the relationship between them. In the case of stoves and dishwashers, both are types of kitchen appliances based on their role in a typical home. You can model the relationship between a stove and a dishwasher using JScript .NET classes and inheritance, as shown in the following listing:

class kitchenAppliance {
  //...
}

class Stove extends kitchenAppliance {
  //...
}

class Dishwasher extends kitchenAppliance {
  //...
}

The code uses the extends keyword to express the relationship between the Stove and Dishwasher classes through the kitchenAppliance class. The Stove and Dishwasher classes derive from the base class kitchenAppliance. You can think of the relationship in another way: a Stove is a specialized type of kitchenAppliance.

Inheritance is a very powerful means of managing complexity since it allows you to compose new types based on existing types, specializing them as necessary. Consider the following listing:

class Dishwasher extends kitchenAppliance {
  // note: this is a partial implementation of the class
  function washDishes()
  {
  print("Washing dishes...done");
  }
}

class Stove extends kitchenAppliance {
  // note: this is a partial implementation of the class
  function boilWater()
  {
  print("Mmmmm....coffee....");
  }
}

The listing demonstrates that the Dishwasher and Stove class each have operations that are specialized for each type - washDishes for the Dishwasher class and boilWater for the Stove class. There's another aspect of inheritance, called polymorphism that allows you to treat specialized types as more general types.

Understanding Polymorphism and Casting

A polymorphic object can assume different forms based on where it exists within its class hierarchy. For example, consider the following listing:

// Note: genericAppliance is a kitchenAppliance type
var genericAppliance : kitchenAppliance = new Dishwasher(); 

print("The appliance is: " + genericAppliance.applianceAsString);
// prints: The appliance is: Dishwasher

The code declares a variable that's a kitchenAppliance type, but creates an instance of a Dishwasher. The code confirms that the genericAppliance variable refers to a Dishwasher by having it print its string representation (third line in the above listing).

JScript .NET allows you to do this because the Dishwasher is polymorphic: you can treat it as a Dishwasher or a kitchenAppliance. Polymorphism allows you to create general functions that take base types for their parameters but operate on more specialized types.

There is a minor catch, though. When you call the washDishes method, and compile the code, the JScript .NET compiler complains since it knows that a kitchenAppliance does not have a washDishes method. You can resolve this by casting the genericAppliance from a kitchenAppliance (its declared type) to a Dishwasher (its actual type), as shown in the following listing:

genericAppliance.washDishes(); // compile-time error...
// 'Objects of type 'kitchenAppliance' do not have such a member'

Dishwasher(genericAppliance).washDishes(); // ok
// prints: Washing dishes...done

The second line of code casts (transforms) the kitchenAppliance into a Dishwasher and then calls the washDishes method. The JScript .NET compiler attempts to confirm that all casts you attempt are legal at compile time. If the JScript .NET compiler allows a case at compile time, which subsequently fails at runtime it raises in Illegal Cast exception.

Understanding how to use Inheritance and Polymorphism

As it stands, the kitchenAppliance class is adequate but has some fundamental design problems:

  • The type member variable is a String, which means that a "stove" and "StoVe" are different types of appliances.
  • The weight member variable is a simple integer type, which does not capture the weight's units (100 pounds, or 100 kilograms?). There aren't any means of converting units of measure from one unit to another.
  • The dimensions of the kitchen appliance share the same problem as the weight member variable. In addition, the dimensions are all simple integers, and do not enforce any type of constraints (you could provide the height measurements and forget to provide a width and depth measurements).

Here's some code that uses a newer implementation of the kitchen appliances sample:

var myFridge = new Fridge();
// the Fridge weighs 350 pounds
myFridge.weight = new quantity(350 , new unitPounds() );
// cubicDimentions has a constructor that makes it easy to record
// initial measurements
myFridge.dimentions = new cubicDimentions(2,new unitMeter(),
  1.5 , new unitMeter(),
  2.5 , new unitMeter());
// display the details of my Fridge...
print(myFridge);
/* output:

Fridge / 350 pounds
  Height: 2 meters
  Width : 1.5 meters
  Depth : 2.5 meters

*/

The code demonstrates that the newer implementation is much easier to use and the code is a lot more concise. Chances are that you would have been able to predict most of the output based only on the above code. The newer implementation goes a little further - consider the following fragment (a continuation of the above sample):

// continued...
var demoQty : quantity;
demoQty = converter.convert(
  myFridge.weight ,
  new quantity( 0 , new unitKg()));
print("Equivilent weight in " + demoQty.units.typeAsString + 
      " is: " + demoQty.amount);
/* output:

Equivilent weight in kilograms is: 159.09091186523438

*/

The fragment demonstrates a conversion class (converter) converting the fridge's weight, from whatever units it happens to currently be in, into a new unit of measure (kilograms). The fragment illustrates that a quantity type encapsulates measurements, like weights and dimensions, making them easier to work with and also shows how the class encapsulates units of measure.

The converter class has a single static function called convert, which performs the conversion from one unit of measure to another. Here's what the convert function looks like:

static function convert(fromQty:quantity ,toQty: quantity) :
                           quantity
{
  var ratio:float;
  var newQty : quantity;
  ratio = toQty.units.conversionRatio(fromQty.units);
  if(ratio == -1) 
    newQty = new quantity( -1,new unit());  
  else
    newQty = new quantity( (ratio*fromQty.amount),
                           toQty.units);
  return newQty;
}

The function takes two parameters, both of which are quantity types and returns a new quantity that represents the new units and units of measure. The line that does all of the work in the function is this one:

newQty = new quantity( (ratio*fromQty.amount),toQty.units);

The line simply multiplies a quantity's amount member by a ratio and constructs a new quantity object. So where does the ratio come from? A quantity has two members: an amount which is a float type and units which is a unit type. Here's part of what a typical unit class looks like:

class unitPounds extends unit
{
  function conversionRatio(intoUnit:unit) : float
  {
    if(intoUnit.type == unitsOfMeasure.kg)
      return (2.2);
    else
      return -1;
  }
}

The unit's conversionRatio member function takes a unit type as a parameter and evaluates it to determine if it's possible to convert from itself to the units that the parameter's unit type uses. If the conversion is possible, the function returns a conversion ratio and returns -1 if the conversion is not possible. If you take a look back at the convert function, you'll see that it checks for the -1 result, otherwise it just performs the conversion using the ratio. This is a simple approach that attempts to encapsulate as much information as possible within each class, thereby relieving class users of having to understand the intricacies of units and their conversion ratios.

The key benefit that the design demonstrates is that classes enable you to work at very high levels of abstraction making code easier to design, write, and maintain. Although you can just as easily implement this sample using only procedural code, it would get very difficult to maintain once the system uses a certain number of units of measure and measurements.

The implementation of the classes uses a number of object oriented programming techniques including inheritance and overloading. Refer to the sample code that accompanies this article for more details.

Downloading and working with the sample code

The sample code is a JScript .NET console application that implements the functionality I described in this article. The primary feature of the application is its code, not its output; as a result, the code does very little with regards to generating output or exercising the application at large. The intent of the sample is primarily to provide an object oriented implementation that you can study, experiment with, and extend. Once you download the application, compile it using the command jsc appliance.js and then run the sample by typing appliance at the command prompt. I originally wrote the application on an early beta version of the .NET Framework and have since modified it to work with .NET version 1.0 - your mileage may vary if you try the application on any beta versions.

  • Download the sample code here
  • Note: you may have to right-click and select "Save As" from the pop-up menu to download the file in case it loads into your browser when you left-click on the link


    Essam Ahmed is the author of "JScript .NET Programming" (ISBN 0764548689, Published by Hungry Minds September 2001), many articles (including some at CodeGuru.com) and book reviews (also available at CodeGuru.com). Contact Essam at essam@designs2solutions.com, or at his Web site



    Comments

    • Inheritance, access, polymorphism blah, blah, blah...

      Posted by Legacy on 03/28/2002 12:00am

      Originally posted by: Stan

      none

      Reply
    Leave a Comment
    • Your email address will not be published. All fields are required.

    Top White Papers and Webcasts

    • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

    • Live Event Date: December 18, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this upcoming webcast …

    Most Popular Programming Stories

    More for Developers

    RSS Feeds