65.9K
CodeProject 正在变化。 阅读更多。
Home

编写可重用软件组件时应考虑的事项

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (1投票)

2010年8月15日

CPOL

8分钟阅读

viewsIcon

21734

一篇定义编写可重用软件时应遵循的一些规则的文章。

引言

根据定义,企业应用程序属于整个组织的范围。信息如何通过适当的管理层级传递通常由行业标准模型定义,例如通用信息模型。从某种意义上说,WMI 是 Microsoft 对 CIM 的实现。由于日常运营定义了开发人员必须处理的数据,因此仅仅编写一个类库可能不符合可重用软件组件的定义。本文将重点介绍编写可重用软件组件,并包含一个可重用软件组件的示例以及该可重用组件的测试/使用。根据 Microsoft .NET Framework 关于编写可重用软件组件的规范,有三个不同的部分

  1. 第一部分涉及编写可重用软件组件的技术,并明确标识这些组件。
  2. 第二部分涉及覆盖用于扩展(或限制)可重用软件组件的适当步骤。
  3. 第三部分讨论了测试和部署可重用软件组件的扩展和包装器。

一般性思考

有人可能会问,一个组件如何(而不是为什么)可以被重用。该组件的可重用性有多大意义?业务运营的唯一一致性就是变化。这些变化有时是计划好的,而其他变化则来自于对竞争的监控或市场突然的变化。当情况不要求重用某个组件时,制造一个重用场景是不可行的,即使它声称“重新发明了轮子”。一个重要的经验法则可能规定,在使用一个必须修改才能满足要求的组件时,请记住继承意味着从原始组件派生一个新组件以扩展所需的额外功能;包装意味着在新组件中创建原始组件的实例以限制功能。几乎所有的重用都以三种基本形式之一出现。代码样本在系统之间复制粘贴。也许,你有一个字符串解析例程,你的同事觉得它很有用。你通过电子邮件将代码发送给他们,他们可能会嵌入一个新的方法。配方是代码样本的扩展,通过这种方式,以使用现有组件的方式来描述重现某些行为的方法。最后,你可以重用在本地或远程系统上分发的二进制文件,而无需将它们与每个产品分发。因此,在这三种基本形式中,有三种基本方法可以重用软件。你可以在多个系统中使用原始形式的组件,可以根据需要为各个系统扩展组件功能,或者可以根据需要为各个系统限制组件功能。

扩展组件

本节的重点是探讨扩展组件的方法,并假设我们正在扩展一个二进制组件。即使源代码可用,我们也想避免修改现有源代码的主题。相反,我们使用继承和多态性来为现有软件添加我们自己的自定义属性和行为。

继承

在本论文撰写时,.NET Framework 的当前(也是迄今为止最强大)版本是 .NET 4.0。然而,单重继承一直由 .NET Framework 2.0 支持。单重继承意味着一个类只能继承自一个类——继承自一个类称为实现继承。也就是说,子类或派生类继承了父类或基类的实际实现或功能。

public class childClass : parentClass

如果基类构造函数未被派生类中的构造函数显式调用,则默认构造函数会被隐式调用。如果没有默认构造函数,则派生类必须调用基类中的构造函数。应该清楚的是,我们不是在重写一个类,而是在编写一个扩展类,该类派生自定义数据成员和原始组件方法的父类。这就是为什么我们必须记住,父类中的并非所有属性和方法都对子类可用,这包括构造函数和析构函数。事实上,构造函数实际上被定义为(在 FCL 中)特殊方法,它们是不可继承的。但在编写可重用软件组件时,访问修饰符会修饰代码元素,以指示其他元素的可见性。下面的列表描述了 .NET Framework 中的可见性修饰符以及每个修饰符如何影响继承

  • 声明为 public 的成员具有无限制的可见性,并且对任何可以看到它们的代码都可用。
  • Private 成员仅在定义它们的类中可用。因此,private 类必须嵌套在其他类中。所有在命名空间级别声明的类都必须是 public。
  • Protected 成员在定义它们的类中的其他成员以及派生自包含其定义类的类的类中可用。声明为 protected 的类必须嵌套在其他类中。
  • Internal 成员在当前程序集内可见。定义 internal 成员的程序集外部的类型看不到 internal 成员。

访问级别(public、protected 和 internal)对于确定组件的哪些功能(如果有)以及如何扩展这些功能(包括整个组件)非常重要。这适用于类定义和成员定义(属性、方法和事件)。除了使用访问级别限制类中的成员可见性之外,还可以控制继承。C# 中的 sealed 关键字可防止派生类。用 abstract 关键字修饰类会要求创建派生类。事实上,C# 中的 abstract 关键字表示基类中必须由派生类实现的成员。以下代码片段演示了这些技术

// the base class cannot be used as is and must be derived
public abstract class MyBaseClass
{
    public abstract decimal calculateInterest(decimal loanAmount, int term);
}
// the derived class cannot, in turn, have another derived class.
public sealed class MySubClass  : MyBaseClass
{
    public override decimal calculateInterest( decimal loanAmount, int term)
    {
         // do some work
    }  
}

回想起来,sealed 可防止类被用作基类。C# 中的 abstract 关键字可防止创建类。该类只能用作派生类的基类。这有时被称为抽象类。C# 类已经是抽象的并且会被实例化。但是你可以像现有基类一样扩展现有实现。通过定义其方法的全新实现来扩展现有实现。重写是用派生类成员替换基类成员,其中派生类成员具有相同的名称。ToString() 方法是一个经常被重写的方法。你可以重写基类中的实现,而不是接受现有的默认实现(如 System.Object 中定义的 ToString)。

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;
    }  
}

.NET Framework(截至 2.0 版本)添加了一个新异常,为继承需要重写类的类提供了额外的灵活性。当你不希望提供实现时,使用 NotImplementedException。使用此异常允许你维护与继承相关的接口合同,而无需实现不合适的特性。

public class subclass : MyBaseClass
{
    public override decimal calculateInterest( decimal loanAmount, int term)
    {
        throw new NotImplementedException();
    }
}

多态

Polymorphism 是一个源自希腊语的词,意思是“多种形式”。举一个脱离上下文的例子,病毒编写者过去会编写在代码中会发生变化的函数。代码循环会显示不同,但功能相同。他们使用“多态引擎”和“解密循环”来欺骗病毒扫描器和安全工具。下面是多态性在正确上下文中的示例

public class Employee
{
   public decimal HoursWorked
   {
       get
       {
           // fetch the hours worked in the database
       }
   }
}

public  class ExemptEmployee :  Employee{}
public class NonExemptEmployee  : Employee {}

public class VacationCalculator
{
    public decimal GetAvailableVacationHours (ExemptEmployee forEmployee)
    {
        { //Compute}
        public decimal GetAvailableVacationHours (NonExemptEmployee forEmployee)
        {//Compute}
    }

这些代码示例应该能向你展示如何使用重载来替换条件逻辑。下面的代码演示了如何以多态方式使用重载的成员

void showOverloadingCalls ()
{
    ExemptEmployee exEmp = new ExemptEmployee();
    NonExemptEmployee  nonexempt = new NonExemptEmployee();
    VacationCalculator vc = new VacationCalculator();
    // the runtime knows which overloaded definition 
    // to call based on the method signature
    vc.GetAvailableVacationHours(exEmp);
    vc.GetAvailableVacationHours(nonexempt);
}

说了这么多。让我们尝试实现这些旨在帮助我们实现可重用软件组件标准的技巧。让我们创建一个名为 ReusableComponentCS 的 C# 类库项目。我们向该项目添加一个名为 TimeCalculator 的类,其定义如下

using System;
using System.Collections.Generic;
using System.Text;

namespace ReusableComponentCS
{
    public class TimeCalculator
    {
        public double CalculatePaidTimeOff(int EmployeeType)
        {
            switch (EmployeeType)
            {
                case 1: // Exempt
                    return 60;
                default:
                    return 20;
            }

        }
        
        public double CalculateVacation(int EmployeeType)
        {
            switch (EmployeeType)
            {
                case 1: // Exempt
                    return 60;
                default:
                    return 20;
            }
        }
        
        public double CalculateHolidays(int EmployeeType)
        {
            switch (EmployeeType)
            {
                case 1: // Exempt
                    return 80;
                default:
                    return 40;
            }
        }
        
        public virtual double CalculateSickTime(int EmployeeType)
        {
            switch (EmployeeType)
            {
                case 1: // Exempt
                    return 12;
                default:
                    return 6;
            }
        }
    }

现在我们向该项目添加一个名为 BenefitsCalculator 的类。我们将此添加简单地追加到 TimeCalculator

using System;
using System.Collections.Generic;
using System.Text;

namespace ReusableComponentCS
{
    public class TimeCalculator
    {
        public double CalculatePaidTimeOff(int EmployeeType)
        {
            switch (EmployeeType)
            {
                case 1: // Exempt
                    return 60;
                default:
                    return 20;
            }

        }
        
        public double CalculateVacation(int EmployeeType)
        {
            switch (EmployeeType)
            {
                case 1: // Exempt
                    return 60;
                default:
                    return 20;
            }
        }
        
        public double CalculateHolidays(int EmployeeType)
        {
            switch (EmployeeType)
            {
                case 1: // Exempt
                    return 80;
                default:
                    return 40;
            }
        }
        
        public virtual double CalculateSickTime(int EmployeeType)
        {
            switch (EmployeeType)
            {
                case 1: // Exempt
                    return 12;
                default:
                    return 6;
            }
        }
    }

    public abstract class BenefitsCalculator
    {
        public double CalculateSTD(int EmployeeType)
        {
            switch (EmployeeType)
            {
                case 1: // Exempt
                    return 240;
                default:
                    return 120;
            }
        }

        public abstract double CalculateRetirement(int EmployeeType);
    }
}

现在是第二部分。显然,先构建第一部分。现在我们将通过添加一个名为 ExtendingRestrictingComponentCS 的类库项目来扩展和限制该组件。向其中添加一个名为 MyTimeCalculator 的类,以扩展和限制 TimeCalculator,其定义如下

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;
        }
    }

现在我们将一个名为 MyBenefitsCalculator 的类添加到项目中,该类通过简单地将其追加到上面的类(并具有以下定义)来扩展和限制 BenefitsCalculator

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)
        {
            // "My" company is a non-profit licensed in the United States.
            // Therefore, use 403B.

            switch (EmployeeType)
            {
                case 1: // Exempt
                    return 403;
                default:
                    return 0;
            }

        }
    }
}

我们当然会构建该组件。现在已经构建好了,请注意代码。公共类 MyTimeCalculator 从基类 TimeCalculator 派生,MyBenefitsCalculator 也是如此。接下来,请注意我们如何重写 CalculateRetirement 方法以使用继承和多态性。现在我们已经构建了这些类,其中一个使用了命名空间(请注意命名空间级别的类是公共的),我们将不得不通过使用它来测试它。所以,我们添加一个名为 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 ();
            // BenefitsCalculator is declared "MustInherit".
            // Therefore we cannot instantiate it directly.

            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 ());
            // Notice that we shadowed the sick time
            // calculation and changed the signature.
            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 ());
            // Notice that we shadowed the sick time calculation and changed the signature.
            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 ());
        }
    }
}

最好使用 Visual Studio 2005(及更高版本)构建此项目,但为了论证起见,我将使用命令行编译类库。假设你在 C 目录正下方创建了一个名为 NET 的文件夹。设置你的环境变量

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

输出

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

通过一个简单的控制台应用程序,该组件得到了测试和进一步使用。

结论

本文已被 MSDN 和 MCTS 考试:Microsoft .NET Framework Application Development Foundation 引用。这本书以及其他 MCTS 书籍应该是任何 .NET 开发者的工具箱中的必备品。

© . All rights reserved.