Introduction
By definition, enterprise applications are within the scope of the entire organization. How information passes through the appropriate managerial level is often defined by an industry-standardized model, such as the Common Information Model. In a sense, WMI is Microsoft's implementation of the CIM. Because the day to day to operations define the data that developers have to work with, simply writing a class library might not adhere to the definition of a reusable software component. This article will focus on writing reusable software components, and will include an example reusable software component and the testing/consuming of this reusable component. According to Microsoft .NET Framework's specifications for writing a reusable software component, there are three distinct sections:
- The first section involves the techniques for writing reusable software components and outright identifying those components.
- The second section involves covering the steps appropriate for extending (or restricting) reusable software components.
- The third section addresses testing and deploying your extensions and wrappers for reusable software components.
General Thoughts
One might ask how, rather than why, a component can be reused. How relevant can that component be that it is reusable? The one consistency about business operations is change. These changes are sometimes planned, while others come from monitoring the competition or sudden changes in the market. It is certainly not viable to fabricate a reuse scenario when circumstances do not dictate the reuse of a certain component, even if it claims to have "reinvented the wheel". One important rule of thumb could dictate that when using a component that must be modified to meet requirements, remember that to inherit means to derive a new component from the original component to extend the needed extra functionality; to wrap means to create an instance of the original component within the new component to restrict functionality. Almost all reuse comes in one of three rudimentary forms. Code samples are copied and pasted among systems. Perhaps, you have a string parsing routine that your coworkers find useful. You email that code to them and they perhaps embed a new method. Recipes are an extension of code samples by which a way to reproduce some behavior is described in terms of consuming an existing component. Finally, you can reuse the binaries distributed on local or remote systems without distributing them with each product. Therefore, within these three rudimentary forms, there are three basic ways to reuse software. You can use the component in its original form in multiple systems, you can extend component functionality as needed for individual systems, or you can restrict component functionality as needed for individual systems.
Extending Components
The point of this section is to explore ways to extend components, and assumes that we are extending a binary component. Even if the source code is available, we want to avoid the topic of modifying existing source code. Instead, we use inheritance and polymorphism to add our own custom attributes and behaviors to existing software.
Inheriting
At the writing of this paper, the current (and most powerful so far) version of the .NET Framework is .NET 4.0. Single inheritance, however, was supported by the .NET Framework 2.0. Single inheritance means that a class can inherit from only one class - inheriting from one class is called implementation inheritance. That is, the child or derived class is inheriting the actual implementation or functionality in the parent or base class:
public class childClass : parentClass
If a base-class constructor is not explicitly called by a constructor in a derived class, the default constructor is called implicitly. If there is no default constructor, the derived class must call a constructor in the base class. It should be clear that we are not rewriting a class, but writing an extension class that derives from the parent class that defines the data members and methods of the original component. This is why we must remember that not every property and method in the parent class is available to the child class, and this includes constructors and destructors. In fact, the constructors are actually defined (as in the FCL) as special methods that are not inheritable. But in writing reusable software components, access modifiers adorn code elements to indicate the visibility of other elements. The following list describes the visibility adornments in the .NET Framework and how each affects inheritance:
- Members declared as public have unrestricted visibility, and are available to any code that can see them.
- Private members are available only in the class in which they are defined. For this reason, private classes must be nested within other classes. All classes declared at the namespace level must be public.
- Protected members are available to other members within the class in which they are defined as well as to classes derived from the class containing their definition. Classes declared as protected must be nested within other classes.
- Internal members are visible within the current assembly. Types outside the assembly in which internal members are defined cannot see the internal members.
Access levels (public, protected, and internal) are important in determining which features (if any) and how those features of a component can be extended, including the entire component. This pertains to the class definition and member definitions (properties, methods, and events). And in addition to restricting member visibility in classes by using access levels, it is also possible to control inheritance. The C# sealed
keyword prevents derivations of a class. Adoring a class with the abstract
keyword requires a derived class to be created. In fact, the C# abstract
keyword indicates members of the base class that must be implemented by a derived class. The following code snippets demonstrate these techniques:
public abstract class MyBaseClass
{
public abstract decimal calculateInterest(decimal loanAmount, int term);
}
public sealed class MySubClass : MyBaseClass
{
public override decimal calculateInterest( decimal loanAmount, int term)
{
}
}
In retrospect, sealed
prevents a class from being used as a base class. The C# keyword abstract
prevents a class from being created. This class can be used only as a base class for a derived class. This is sometimes called an abstract class. A C# class is already an abstraction and is instantiated. But you can extend an existing implementation of a base class as it is. Extend the existing implementation by defining new implementations of its methods. Overriding is replacing a base class member with a derived class member of the same name. The ToString()
method is an often overridden method. Instead of accepting the existing the default implementation of ToString
(as defined by System.Object
), you can override the implementations in the base class:
public abstract class MyBaseClass
{
public abstract decimal calculateInterest(decimal loanAmount, int term);
}
public class MySubClass : MyBaseClass
{
public override decimal calculateInterest( decimal loanAmount, int term)
{
return 15.5M;
}
}
The .NET Framework (as of version 2.0) adds a new exception to give us additional flexibility when inheriting from classes requiring overriding. NotImplementedException
is used when you do not want to provide an implementation. Using this exception allows you to uphold the interface contract associated with inheritance without requiring you to implement features that aren't appropriate:
public class subclass : MyBaseClass
{
public override decimal calculateInterest( decimal loanAmount, int term)
{
throw new NotImplementedException();
}
}
Polymorphism
Polymorphism is a word with Greek roots that means "many forms". To provide an out-of-context example, virus writers used to write functions that would change in the code. The code loop would appear different, but the functionality would be the same. They used a "polymorphic engine" and a "decryption loop" to dupe virus scanners and security tools. Below is an example of polymorphism in proper context:
public class Employee
{
public decimal HoursWorked
{
get
{
}
}
}
public class ExemptEmployee : Employee{}
public class NonExemptEmployee : Employee {}
public class VacationCalculator
{
public decimal GetAvailableVacationHours (ExemptEmployee forEmployee)
{
{
public decimal GetAvailableVacationHours (NonExemptEmployee forEmployee)
{
}
These code examples should show you how you can use overloading to replace conditional logic. The following code demonstrates how to use the overloaded members polymorphically:
void showOverloadingCalls ()
{
ExemptEmployee exEmp = new ExemptEmployee();
NonExemptEmployee nonexempt = new NonExemptEmployee();
VacationCalculator vc = new VacationCalculator();
vc.GetAvailableVacationHours(exEmp);
vc.GetAvailableVacationHours(nonexempt);
}
So enough said. Let's try and implement these techniques that mean to help us implement the criterion behind reusable software components. Let's create a C# Class Library project called ReusableComponentCS. We'll add a class to the project called TimeCalculator
, with the following definition:
using System;
using System.Collections.Generic;
using System.Text;
namespace ReusableComponentCS
{
public class TimeCalculator
{
public double CalculatePaidTimeOff(int EmployeeType)
{
switch (EmployeeType)
{
case 1:
return 60;
default:
return 20;
}
}
public double CalculateVacation(int EmployeeType)
{
switch (EmployeeType)
{
case 1:
return 60;
default:
return 20;
}
}
public double CalculateHolidays(int EmployeeType)
{
switch (EmployeeType)
{
case 1:
return 80;
default:
return 40;
}
}
public virtual double CalculateSickTime(int EmployeeType)
{
switch (EmployeeType)
{
case 1:
return 12;
default:
return 6;
}
}
}
Now we add a class to the project called BenefitsCalculator
. We simple append this addition to the TimeCalculator
class:
using System;
using System.Collections.Generic;
using System.Text;
namespace ReusableComponentCS
{
public class TimeCalculator
{
public double CalculatePaidTimeOff(int EmployeeType)
{
switch (EmployeeType)
{
case 1:
return 60;
default:
return 20;
}
}
public double CalculateVacation(int EmployeeType)
{
switch (EmployeeType)
{
case 1:
return 60;
default:
return 20;
}
}
public double CalculateHolidays(int EmployeeType)
{
switch (EmployeeType)
{
case 1:
return 80;
default:
return 40;
}
}
public virtual double CalculateSickTime(int EmployeeType)
{
switch (EmployeeType)
{
case 1:
return 12;
default:
return 6;
}
}
}
public abstract class BenefitsCalculator
{
public double CalculateSTD(int EmployeeType)
{
switch (EmployeeType)
{
case 1:
return 240;
default:
return 120;
}
}
public abstract double CalculateRetirement(int EmployeeType);
}
}
Now for the second part. Obviously, build the first part. Now we are going to be extending and restricting the component by adding a Class Library project called ExtendingRestrictingComponentCS. To that, we add a class called MyTimeCalculator
in order to extend and restrict TimeCalculator
with the following definition:
using System;
using System.Collections.Generic;
using System.Text;
using ReusableComponentCS;
namespace ExtendingRestrictingComponentCS
{
public class MyTimeCalculator : TimeCalculator
{
public new double CalculateSickTime(int EmployeeType)
{
return 0;
}
}
Now we add a class called MyBenefitsCalculator
to the project that extends and restricts the BenefitsCalculator
simply by appending it to the above class (and having the following definition):
using System;
using System.Collections.Generic;
using System.Text;
using ReusableComponentCS;
namespace ExtendingRestrictingComponentCS
{
public class MyTimeCalculator : TimeCalculator
{
public new double CalculateSickTime(int EmployeeType)
{
return 0;
}
}
public class MyBenefitsCalculator : BenefitsCalculator
{
public override double CalculateRetirement(int EmployeeType)
{
switch (EmployeeType)
{
case 1:
return 403;
default:
return 0;
}
}
}
}
We, of course, build the component. Now that it is built, notice the code. The public class MyTimeCalculator
extends from the base class TimeCalculator
, as does MyBenefitsCalculator
. Next, note how we override the CalculateRetirement
method to use inheritance and polymorphism. Now that we have these classes built, one using the namespace (notice that the class at the namespace level is public), we'll have to test it by consuming it. So now, we add a Console Application project called ReusableTestCS:
using System;
using System.Collections.Generic;
using System.Text;
using ReusableComponentCS;
using ExtendingRestrictingComponentCS;
namespace ReusableComponentTestCS
{
class Program
{
static void Main(string[] args)
{
CalculateForAnyCompany();
CalculateForMyCompany();
Console.ReadLine();
}
private static void CalculateForAnyCompany()
{
TimeCalculator timecalc = new TimeCalculator ();
Console.WriteLine("Employee Time Calculations - Any Company");
Console.WriteLine("-----------------------------------------------");
Console.WriteLine("Exempt Holiday Time: " +
timecalc.CalculateHolidays(1).ToString ());
Console.WriteLine("Exempt Vacation Time: " +
timecalc.CalculateVacation(1).ToString ());
Console.WriteLine("Exempt Paid Off Time: " +
timecalc.CalculatePaidTimeOff(1).ToString ());
Console.WriteLine("Exempt Sick Time: " +
timecalc.CalculateSickTime(1).ToString ());
Console.WriteLine();
Console.WriteLine("Non Exempt Holiday Time: " +
timecalc.CalculateHolidays(0).ToString ());
Console.WriteLine("Non Exempt Vacation Time: " +
timecalc.CalculateVacation(0).ToString ());
Console.WriteLine("Non Exempt Paid Off Time: " +
timecalc.CalculatePaidTimeOff(0).ToString ());
Console.WriteLine("Non Exempt Sick Time: " +
timecalc.CalculateSickTime(0).ToString ());
Console.WriteLine();
}
private static void CalculateForMyCompany()
{
BenefitsCalculator benecalc = new MyBenefitsCalculator();
TimeCalculator timecalc = new MyTimeCalculator();
Console.WriteLine("Employee Time Calculations - My Company");
Console.WriteLine("----------------------------------------------");
Console.WriteLine("Exempt Holiday Time: " +
timecalc.CalculateHolidays(1).ToString ());
Console.WriteLine("Exempt Vacation Time: " +
timecalc.CalculateVacation(1).ToString ());
Console.WriteLine("Exempt Paid Off Time: " +
timecalc.CalculatePaidTimeOff(1).ToString ());
Console.WriteLine("Exempt Sick Time: " +
timecalc.CalculateSickTime(-1).ToString ());
Console.WriteLine();
Console.WriteLine("Non Exempt Holiday Time: " +
timecalc.CalculateHolidays(0).ToString ());
Console.WriteLine("Non Exempt Vacation Time: " +
timecalc.CalculateVacation(0).ToString ());
Console.WriteLine("Non Exempt Paid Off Time: " +
timecalc.CalculatePaidTimeOff(0).ToString ());
Console.WriteLine("Non Exempt Sick Time: " +
timecalc.CalculateSickTime(-1).ToString ());
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("Employee Benefits Calculations - My Company");
Console.WriteLine("--------------------------------------------------");
Console.WriteLine("Exempt STD Time: " +
benecalc.CalculateRetirement(1).ToString ());
Console.WriteLine("Exempt Retirement Time: " +
benecalc.CalculateSTD(1).ToString ());
Console.WriteLine();
Console.WriteLine("Non Exempt STD Time: " +
benecalc.CalculateRetirement(0).ToString ());
Console.WriteLine("Non Exempt Retirement Time: " +
benecalc.CalculateSTD(0).ToString ());
}
}
}
This project is best built with Visual Studio 2005 (and up), but I am going to compile the class libraries on the command line for the sake of argument. Assume you make a folder called NET right off of the C directory. Set your environmental path:
set PATH=%PATH%;.;C:\Windows\Microsoft.NET\Framework\v4.0.30319
C:\NET>csc /t:library ResuableComponentCS.cs
C:\NET>csc /t:library /r: ReusableComponentCS.dll ExtendingRestrictingComponentCS.cs
C:\NET> csc /r:ReusableComponentCS.dll /r:ExtendingRestrictingComponentCS.dll MyProgram.cs
. . .
The output:
Employee Time Calculations - Any Company
-----------------------------------------------
Exempt Holiday Time: 80
Exempt Vacation Time: 60
Exempt Paid Off Time: 60
Exempt Sick Time: 12
Non Exempt Holiday Time: 40
Non Exempt Vacation Time: 20
Non Exempt Paid Off Time: 20
Non Exempt Sick Time: 6
Employee Time Calculations - My Company
----------------------------------------------
Exempt Holiday Time: 80
Exempt Vacation Time: 60
Exempt Paid Off Time: 60
Exempt Sick Time: 6
Non Exempt Holiday Time: 40
Non Exempt Vacation Time: 20
Non Exempt Paid Off Time: 20
Non Exempt Sick Time: 6
Employee Benefits Calculations - My Company
--------------------------------------------------
Exempt STD Time: 403
Exempt Retirement Time: 240
Non Exempt STD Time: 0
Non Exempt Retirement Time: 120
And the component is tested and further consumed by a simple console application.
Conclusion
This article has been referenced by the MSDN and the MCTS Exam: Microsoft .NET Framework Application Development Foundation. This and the other MCTS books should be a must in any .NET developer's tool chest.