本文回顾了多态性概念, 示范了XML Web服务。最重要的是本文将教你如何把多态性和Web服务结合起来。
一、简介
你可能知道多态性,或许也知道Web服务。但是跨越Web服务的多态性又是怎样的呢? 本文回顾了多态性概念, 示范了XML Web服务。最重要的是本文将教你如何把多态性和Web服务结合起来。
二、多态性
那些熟悉面向对象编程 (OOP)的读者应该对多态性非常熟悉,但并不是每个人都熟悉面向对象编程。 如果你是前一个读者群,可以直接跳到“XML Web服务”一节。 如果你是后者,请继续阅读。
在面向对象编程语言出现之前,如果你想要打印不同类型的数据,需要写多个方法 ,象是PrintInteger(int i),PrintString(string s) 和 PrintFloat(float f) 。也就是说, 你必须通过命名来区别行为和数据类型,因为 OOP语言出现前任一语言象是C,不允许你用相同的名字写方法, 即使他们的参数类型不同。
C++的来到实现了方法重载。因此,你可以写多个方法 , 象是 PrintInteger(int i)、PrintString(string s) 和 PrintFloat(float f),编译器自会准确调用特定的Print方法。方法重载被一种称为名称重整(name mangling)的技术所支持,在这种技术中,编译器通过把原方法名称与其参数相结合产生一个独特的内部名字来取代原方法名称。 如此,当你调用Print(1)的时候, 编译器可能在内部用源于参数类型的前缀重命名Print方法,这样一来Print(1)可能就变成 i_Print (1) 。
方法重载仅是多态性的一种情形。 名称重整是一种支持方法重载的机制。更普遍的情况下,多态性是与继承相联系。 什么是继承呢?继承就是一个新类 (称为子类) 从被继承类(称为父类或超类)取得自身的部分定义同时增加一些自己的新的信息。 如果你在相同的类中重载方法, 数据类型必须是不同的。如果你在继承关系下重载方法, 子类与父类的方法可能完全相同,而且名称重整器生成同样的重整名称。
举例来说, 假设一个超类定义一个Print(int i)方法而一个从它继承的子类也定义了一个Print(int i)方法。当你有一个子类的实例时,运用多态性调用Child.Print(int);而当你产生一个父类的实例时运用多态性调用Parent.Print(int)。这就是继承多态性:相同的名字和签字但是类却不同。
继承多态性是通过使用一种与名称重整相关的另外一种机制实现的。编译器把方法放置在一个被称为虚拟方法表(其实是一个方法数组)的地方。每一个方法在VMT中都有一个索引, 如此当Print(int)被调用的时候, 编译器将被路由到VMT处找寻Print方法和类的内在索引。这样一来,编译器就可以调用正确的方法实现。由编译器负责管理所有的VMT索引和类偏移量。
简言之,多态性使你能够用非常相似的名字定义许多方法,这里的名字往往都是直观易记的。 OOP编译器自会根据调用者类理解到底该调用哪个方法。多态性的最大好处之一就是,你不再必须写下面这样的代码了(这里使用的仅是描述性语言):
If type of arg is integer then
PrintInteger(arg)
Else if type of arg is string then
PrintString(arg)
Else if type of args is float then
PrintFloat(arg)
现在,有了OOP语言,上面的表达只需用一句即可:
Print(arg)
编译器的多态机制通过生成一个方法索引(这实际上相当于上面的条件语句),自会计算出应调用print方法的哪一个版本。
要从语言的角度了解关于OOP内部工作机制的权威描述,可以参阅Bjarne Stroustrup的《The C++ Programming Language》 (ISBN: 0201700735)(Addison Wesley公司出版)。注意,许多OOP语言使用与C++非常相似的机制。
三、XML Web服务
如果你对XML Web服务和它的应用有所了解,那么你对XML Web服务的技巧和引入动机的理解应毫无困难,可以直接跳越到下一节“用Web服务支持多态性”。
在过去十年左右时间当中, 分布式应用已经变得愈来愈普遍。象许多其他类型的工程学一样, 软件行业经历了发明然后标准化的时期。XML Web服务是一种基于HTTP和XML开放协议的标准,它不独属于微软,但是微软确实提供了基于.NET架构及其特性的XML Web服务的实现。
基本的思想是,通过你的编码给描述Web 服务的类添加WebServiceAttribute,并给该类中的Web方法或者是允许消费者调用的方法加上WebMethodAttribute属性。微软的实现技术是,使用反射与代码生成技术来产生代理类型和代理代码,这使得调用分布式服务和方法变得容易。除了产生代理代码之外, .NET架构和Visual Studio还包含一个向导来为你代理Web服务和Web方法。
为产生一个Web服务,运行Visual Studio .NET,然后选择File-New-Project(本文选工程类型为“Visual Basic Projects”),从新的工程对话框的模板列表中选择“ASP.NET Web Service ”。
要运行该示例Web服务及Web方法,去掉工程模板向导提供的方法HelloWorld前面的注释部分,然后运行该方案。要了解更多的关于XML Web服务的产生及应用等方面的信息,可以参看前面的“VB Today”栏目, 特别是《Building Distributed Apps?Use XML Web Service, Not Remoting (Mostly)》(December 2004)。
四、对基于XML生成的代理类添加多态性支持
现在,将你的注意力转向这一文章的目的。
当你为Web方法定义参数而且返还参数的时候,一个被称为网络服务发现语言 (WSDL) 的公用程序激活另一个被称为 SPROXY的工具。SPROXY使用反射和CodeDOM技术来为你的Web方法中声明的类型勾划出一个定义,然后为复合类型产生代理类。举例来说,如果你有一个叫做Person的类,当消费者使用该Web服务时, SPROXY 将会为你生成一个Person类。其优点是,Web服务生产者不必要因消费者要使用他们的代码而把他们的专有代码发送给消费者。SPROXY 为他们做了工作。通过使用代理代码,在商业上的私有生意规则得到保护的同时,仍然实现销售之目的――让客户存取这些规则提供的特性。
下面的代码描述了一个Person类和一个生成的Person代理。
列表 1. 在Web服务背后的Person类
public class Person
{
private string name;
public Person(){}
public Person(string name)
{
this.name = name;
}
public string Name
{
get{ return name; }
set{ name = value; }
}
public string GetUpperName()
{
return name.ToUpper();
}
public string UpperName
{
get{ return GetUpperName(); }
set{}
}
}
列表 2.由 SPROXY所产生的Person类的代理版本
public class Person
{
/// <remarks/>
public string Name;
}
如你所见,没有一个版本包含任何的私有信息。网络服务并不自动判断或者要求你加入自己的技术保护――保留私有的商业规则仅仅是一种副产品罢了,因为消费者拿到的代理类是一个不包含任何方法的结构而已。
大概SPROXY所能做的就是,反射Web方法的类型并将公共的属性转换成公共的域。这对于经由Web服务在客户和服务器间发送和接收数据已是足够了。
除此之外,强类型集合要被转换成强类型数组。举例来说,一个从 System.Collections.CollectionBase(关于强类型集合,请参阅我写的《Visual Basic .NET Power Coding》(Addison Wesley公司出版)一书)派生的PersonCollection集合将被代理生成为一个数组Person或Person()。矛盾在这里:如果Person类是一个抽象类而创建PersonCollection集合的目的是用于包含任何从派生的类如Employee或Customer,那该怎么办?没有一些特别的帮助, XML Web服务将会产生一个Person代理类但却不知道关于Employee或Customer的任何事( 见下面列表3)。这在技术上意味着,如果你返回一个Employees数组以满足Person集合,消费者程序仍会成功编译但在运行时会崩溃。 列表3. 集合PersonCollection 和类Person, Employee, and Customer的定义
using System;
using System.Reflection;
using System.Diagnostics;
using System.Xml.Serialization;
namespace BusinessCollections
{
[Serializable()]
public class PersonCollection : System.Collections.CollectionBase
{
public static PersonCollection CreateNew()
{
PersonCollection persons = new PersonCollection();
persons.Add(new Person("Paul"));
persons.Add(new Customer("David", "Cadyville"));
persons.Add(new Employee("Kathy", 50000M));
return persons;
}
public Person this[int index]
{
get{ return (Person)List[index]; }
set{ List[index] = value; }
}
public int Add(Person value)
{
return List.Add(value);
}
public PersonCollection Select(Type type)
{
PersonCollection persons = new PersonCollection();
foreach(Person p in List)
{
if( p.GetType().Equals(type))
persons.Add(p);
}
return persons;
}
public void Dump()
{
foreach(Person person in this)
{
Debug.WriteLine(string.Format("Type: {0}",person.GetType().FullName));
PropertyInfo[] properties = person.GetType().GetProperties();
foreach(PropertyInfo p in properties)
{
try
{
Debug.WriteLine(string.Format("Name: {0}, Value: {1}",p.Name, p.GetValue(person, null)));
}
catch
{
Debug.WriteLine(string.Format("Name: {0}, Value: {1}",p.Name, "unknown"));
}
}
}
}
}
public class Person
{
private string name;
public Person(){}
public Person(string name)
{
this.name = name;
}
public string Name
{
get{ return name; }
set{ name = value; }
}
public string GetUpperName()
{
return name.ToUpper();
}
public string UpperName
{
get{ return GetUpperName(); }
set{}
}
}
public class Employee : Person
{
private decimal salary;
public Employee() : base(){}
public Employee(string name) : base(name){}
public Employee(string name, decimal salary) : base(name)
{
this.salary = salary;
}
public decimal Salary
{
get{ return salary; }
set{ salary = value; }
}
}
public class Customer : Person
{
private string city;
public Customer() : base(){}
public Customer(string name) : base(name){}
public Customer(string name, string city) : base(name)
{
this.city = city;
}
public string City
{
get{ return city; }
set{ city = value; }
}
}
}
列表3的重点在于,Employee和Customer派生于Person但Web服务仅仅了解Person集合。同时,为示范起见,PersonCollection在静态方法PersonCollection.CreateNew中创建了Person、 Employee和Customer的实例。
如果你要写一个返回一个称为GetPerson的PersonCollection的实例的Web方法(参列表4),SPROXY将仅生成一个代理类Person,而且返回类型变为Person()。
列表 4: 返回一个PersonCollection实例的WebMethod:
[WebMethod]
public PersonCollection GetPeople()
{
return PersonCollection.CreateNew();
}
如果列表4是你提供给消费者的全部, 那么,虽然他们的代码编译成功,但是当从Web方法返回的Person数组被初始化时,可怜的消费者将会遇到一个运行时刻错误抛出SoapException 。最后,因为你定义了其他一些派生于Person的类,所以你也应该设法使你的Web服务消费者能够使用这些类型。
既然你已经了解了问题的一切, 修改就很容易了。使用在 System.Xml.Serialization中定义的 XmlInclude属性来指定另外一些类型――消费者方也需要为它们生成代理类 。在类自身的首部加上XmlInclude属性,用类型对象初始化它――这些类型对象用于每一个需要代理的额外类型。列表5 展示了Web服务的类的首部(该类包含了GetPeople方法)定义