至此,关于SAO对象编译和发布的问题已经解决。再让我们回过头来看一下关于CAO对象的类似问题,是否可以同样通过使用接口来解决问题呢?
照例,我们从一个完整的应用实例开始:
//share.cs, Remote Object
namespace Effective.CSharp.Chapter4 {
//Exactly same as the original one
public class RemoteObject : System.MarshalByRefObject {
//a very simple method implementation
public string SayHello(string name) {
return "Hello, " + name;
}
}
}
//server.cs, Server side code
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace Effective.CSharp.Chapter4 {
public class Server {
public static int Main() {
//Register the channel
TcpChannel chan = new TcpChannel(8085);
ChannelServices.RegisterChannel(chan);
//Register the client activated remote class
RemotingConfiguration.ApplicationName = “MyServer”;
RemotingConfiguration.RegisterActivatedServiceType(
typeof(RemoteObject));
//Hold the server, wait for client
System.Console.WriteLine("Hit <enter> to exit...");
System.Console.ReadLine();
return 0;
}
}
}
//client.cs, Client Side code
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace Effective.CSharp.Chapter4 {
public class Client
{
public static int Main()
{
TcpChannel chan = new TcpChannel();
ChannelServices.RegisterChannel(chan);
//Register at client side
RemotingConfiguration.RegisterActivatedClientType(
typeof(RemoteObject), "tcp://localhost:8085/MyServer");
//Create the remote object
RemoteObject obj = new RemoteObject();
Console.WriteLine(obj.SayHello("World"));
return 0;
}
}
}
显然,在CAO对象的编译和发布方面,我们会碰到与SAO对象完全相同的问题。Client.cs的编译和运行离不开share.dll。CAO对象的应用还有几处明显的不同:CAO对象不仅要求在服务器端进行注册,而且在客户端也需要完成一定的注册工作;更重要的是,CAO对象是通过new创建的!后者彻底阻断了简单的通过接口解决问题的幻想——不可能通过new创建出接口的实例,而必须显式的指明要创建的实现特定接口的类型名称!
先来看一下.NET内部是如何根据客户请求创建CAO对象的。.NET Remoting服务会注册一个通用的Singleton SAO对象,用于处理所有创建CAO对象的请求。这个SAO对象实现了IActivator接口,其缺省URI为<application name>/RemoteActivationService.rem,其中application name可以通过RemotingConfiguration. ApplicationName进行设置。
当客户程序执行new语句创建远程对象时,实际上客户端的.NET Remoting服务会通过远程对象的URL地址连接到服务器端相应的RemoteActivationService.rem入口(上面的例子中是"tcp://localhost:8085/MyServer/RemoteActivationService.rem"),调用SAO对象IActivator接口的Activate方法;Activate方法会根据参数查找所有在服务器端注册过的CAO对象类型,如果找到相对应的注册信息(包括类型名称、所在的类集以及版本等),Activate就会根据这些信息创建一个该对象的实例,并且为新创建的对象生成一个唯一的URI入口,然后将该对象返回给客户端。这样,此后的客户方法调用就会与这个特定的CAO对象实例对应起来。(其中为新生成对象指定URI的部分会由底层的.NET Remoting服务自动完成,Activate需要做的仅仅是生成一个新的对象,并且返回该对象的引用)。
可见,远程对象的客户激活机制仅仅是架设在SAO基础上的一层包装,在简化程序员工作的同时,这种客户激活机制也剥夺了对CAO对象使用接口和抽象基类的权力。既然.NET对CAO对象的实现没有用到任何特别的方法,我们就完全可以模仿系统实现自己的CAO机制,只需少许更改,就可以为子定义的CAO提供基于接口的创建方法。
不难看出在整个CAO实现体系中,IActivator实际上是作为一个通用类厂(Class Factory)接口出现的,所以我们也需要针对IRemoteObject接口定义自己的类厂接口IRemoteFactory:
//share.cs, Remote Object and Factory interface
namespace Effective.CSharp.Chapter4 {
public interface IRemoteObject {
string SayHello(string name);
}
public interface IRemoteFactory {
IRemoteObject CreateInstance();
}
}
在服务器端的代码中,我们需要实际实现RemoteObject和一个用于创建RemoteObject的类厂,并且将后者注册为SAO对象:
//server.cs, Server side code
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace Effective.CSharp.Chapter4 {
public class RemoteObject: MarshalByRefObject,
IRemoteObject {
public string SayHello(string name) {
return “Hello, “ + name;
}
}
public class RemoteFactory: MarshalByRefObject,
IRemoteFactory {
public IRemoteObject CreateInstance() {
return new RemoteObject();
}
}
public class Server {
public static int Main() {
//Register the channel
TcpChannel chan = new TcpChannel(8085);
ChannelServices.RegisterChannel(chan);
//Register the client activated remote class
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RemoteFactory),
"MyFactory.rem", WellKnownObjectMode.Singleton);
//Hold the server, wait for client
System.Console.WriteLine("Hit <enter> to exit...");
System.Console.ReadLine();
return 0;
}
}
}
最后,客户端的实现代码也略有不同:必须先通过Activator.GetObject得到IRemoteFactory接口,然后在需要创建RemoteObject的时候调用IRemoteFactory.CreateInstance:
//client.cs, Client Side code
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace Effective.CSharp.Chapter4 {
public class Client
{
public static int Main()
{
TcpChannel chan = new TcpChannel();
ChannelServices.RegisterChannel(chan);
IRemoteFactory factory =
(IRemoteFactory)Activator.GetObject(
typeof(IRemoteFactory), "tcp://localhost:8085/MyFactory");
//Create the remote object
RemoteObject obj = factory.CreateInstance();
Console.WriteLine(obj.SayHello("World"));
return 0;
}
}
}
在以上所有应用接口的实例当中,我们同样可以使用抽象基类代替接口(关于抽象基类和接口的比较,详见条款X)。
对于应用.NET Remoting技术的分布式应用程序开发,使用接口以及基于接口的类厂是比较好的设计方案,因为这种方案不仅提供了更加清晰的应用模块结构,而且具有更好的可扩展性。