jillzhang

专注才能专业

随笔- 241  文章- 0  评论- 4191 
博客园  首页  新随笔  联系  管理  订阅 订阅

WCF从理论到实践(17):OO大背离(带视频+ppt+源码)

如果您懒得看下面的文字,您按下面的提示下载视频教程,里面还有ppt和源代码

请您到:http://downloads.cnblogs.com/jillzhang/wcf17.rar 下载视频+课件+源码,多谢dudu提供了这么好的交流空间。上篇文章中,大家提了好多好的建议,本次视频文件大小已经大大减小,而且将pptx转换成了ppt,多谢兄弟们的支持和帮助。以后想从事培训,有此方面门路的兄弟如果能推荐一下,更是不胜感激。

概述

上文说到了WCF和传统面向对象编程中不太一致的地方之一:操作重载(Operation Overload),本文讲述WCF 另外一个不太符合OO之处:服务契约和数据契约的继承关系。在面向对象的大原则中有下面两个原则

1) 依赖倒置原则
2) Liskov替换原则

依赖倒置原则强调的是实现依赖于抽象,抽象不依赖于实现 ,而Liskov原则强调的是子类必须可以替换其基类,这在anytao大作<<你必须知道的.Net>>中都有详细的阐述。本文无意阐述这两个原则的细节,想了解OO原则的知识,可以阅读王兄的大作。本文只探讨WCF架构下对这两个原则的辩证统一关系。

WCF架构的特征

在弄清楚WCF在上两个OO原则矛盾统一的关系之前,我想有必要先了解WCF的架构,清楚了WCF架构之后,才能更清楚地明白为何WCF中对上述原则的辩证关系!我们先来看下WCF 通讯的工作原理

 

请看上面的WCF体系结构图(该图原出处<<WCF服务编程>>一书),从图中我们看出WCF通讯双方是存在明显的分界的,尽管WCF也支持in-proc,但这种分界依然存在。我们知道接口和抽象类都是对现实世界的一种抽象描述,它们基于的是现实中的真实场景。比如公鸡能报晓,猴子能上树,老鼠能盗洞,公鸡母鸡都是鸡,鸡鸭鹅全是家禽等等。这些都是人类在长期社会生活中,对现实世界的一种认识!这种认识是存在地域特性的,比如有些地区视蛇为毒虫猛兽,如果给蛇作个接口的话,会包含如下云云:void EatPeople();它会吃人,这种印象很不好,但是另外一些地区可能就将蛇作为图腾,他们眼中蛇是神圣的,如果让他们描述蛇,他们会说: void ProtectPeople();蛇能庇佑人类!同样对事物的分类也是如此。隐喻到软件开发中,我们在一个边界下定义的接口规范和类的层次对于其他边界下的系统是否一定通用呢?答案是否定的。在WCF中,服务与客户是完全松散的耦合,他们之间完全没有必要了解对方的具体实现,如果不是用到WCF,二者老死都可以不相往来。但二者之间加入WCF之后便有了联系,我的理解是代理(Proxy)便是二者之间的红娘,它起到了桥梁,纽带,中介的作用。既然是中介,那么他就应该一碗水端平,不能因为服务端的自身问题给客户端带来不必要的负担,反之亦然!也就是说WCF服务端定义的一些层级概念是服务端的规范,这些规范针对客户端来说,是否适用,那要看客户端的具体业务逻辑,所以代理这个红娘就不能将服务端的逻辑强加给客户端。下面我们就从服务契约(ServiceContract)的层级关系和数据协定(DataContract)的层级关系两个方面看看WCF框架是如何体现上述的特征的。

服务契约的层级关系

闲言少叙,我们采用下面的场景来做演示,场景如下:

Mp3是一个能播放音乐的机器,而录音机是一款能录音的机器,当前的大部分手机呢,除了原有的接打电话,收发短信等功能,它还有一些扩展功能,比如播放音乐的功能,录音的功能。而对于现实中某些个体而言,他可能只会用到手机功能的全部或者一部分,比如一个人用到了全部的功能,它所认识的手机便是:能播打电话,能收发短信,能放歌,能录音的机器,而另外一个人他只用到放歌的功能,对于他来讲,手机就= mp3播放器,同理,如果他只用到录音功能,那在他看来手机就是个录音机。

用程序实现如下,按照WCF实现的通常步骤,我们先来实现契约部分,契约部分我们定义三个服务契约:

IMp3.cs

[ServiceContract] 

public interface IMp3 

{ 

[OperationContract] 

void PlaySound(string soundFile); 

}
 

IRecorder.cs

[ServiceContract] 

public interface IRecorder 

{ 

[OperationContract] 

void Record(); 

}
 

ITelephone.cs

[ServiceContract] 

public interface ITelephone:IMp3,IRecorder 

{ 

[OperationContract] 

void Call(string to); 

[OperationContract] 

void Pickup(string from); 

[OperationContract] 

void ReceiveMessage(string from); 

[OperationContract] 

void SendMessage(string to); 

}
 

接下来,我们实现服务的实现部分,我们在服务实现中,只需要实现一个Telephoe便可以完成契约中全部的功能列表了

Telephone.cs

Telephone.cs
using log = System.Console; 

public class Telephone:Contracts.ITelephone 

{ 

public void Call(string to) 

{ 

log.WriteLine(
"telephone is calling"); 

}
 

public void Pickup(string from) 

{ 

log.WriteLine(
"telphone is pickuping."); 

}
 

public void ReceiveMessage(string from) 

{ 

log.WriteLine(
"telephone is receiving private message"); 

}
 

public void SendMessage(string to) 

{ 

log.WriteLine(
"telephone is sending private message"); 

}
 

public void PlaySound(string soundFile) 

{ 

log.WriteLine(
"telephone is playing"); 

}
 

public void Record() 

{ 

log.WriteLine(
"telephone is recording"); 

}
 

}
 

此时我们先来看一下服务端服务契约的层级关系图:

下面我们实现一个托管,这部分代码不重要,和本文想要阐述的知识也不太相关,所以可以略过,只知道我们上一步中的服务已经被托管起来了。托管代码为:

托管代码
Uri baseAddress = new Uri("net.tcp://127.0.0.1:1236"); 

ServiceHost host 
= new ServiceHost(typeof(Services.Telephone), baseAddress); 

host.AddServiceEndpoint(
typeof(Contracts.IMp3), new NetTcpBinding(),"mp3"); 

host.AddServiceEndpoint(
typeof(Contracts.IRecorder), new NetTcpBinding(), "recorder"); 

host.AddServiceEndpoint(
typeof(Contracts.ITelephone), new NetTcpBinding(), "tel"); 

ServiceMetadataBehavior metaBehavior 
= host.Description.Behaviors.Find<ServiceMetadataBehavior>(); 

if (metaBehavior == null) 

{ 

metaBehavior 
= new ServiceMetadataBehavior(); 

host.Description.Behaviors.Add(metaBehavior); 

}
 

BindingElement bindingElement 
= new TcpTransportBindingElement(); 

CustomBinding metaBind 
= new CustomBinding(bindingElement); 

host.AddServiceEndpoint(
typeof(IMetadataExchange), metaBind, "MEX"); 

host.Open(); 

Console.WriteLine(
"service is running"); 

Console.Read(); 

host.Close(); 

用Svcutil.exe生成代理文件Proxy.cs,打开它,我们会发现如下的代码:

生成的代理代码
//------------------------------------------------------------------------------ 

// <auto-generated> 

// 此代码由工具生成。 

// 运行库版本:2.0.50727.1433 

// 

// 对此文件的更改可能会导致不正确的行为,并且如果 

// 重新生成代码,这些更改将会丢失。 

// </auto-generated> 

//------------------------------------------------------------------------------ 
 

[System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.ServiceModel", "3.0.0.0")] 

[System.ServiceModel.ServiceContractAttribute(ConfigurationName
="IMp3")] 

public interface IMp3 

{ 

 

[System.ServiceModel.OperationContractAttribute(Action
="http://tempuri.org/IMp3/PlaySound", ReplyAction="http://tempuri.org/IMp3/PlaySoundResponse")] 

void PlaySound(string soundFile); 

}
 

 

[System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.ServiceModel", "3.0.0.0")] 

public interface IMp3Channel : IMp3, System.ServiceModel.IClientChannel 

{ 

}
 

 

[System.Diagnostics.DebuggerStepThroughAttribute()] 

[System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.ServiceModel", "3.0.0.0")] 

public partial class Mp3Client : System.ServiceModel.ClientBase<IMp3>, IMp3 

{ 

 

public Mp3Client() 

{ 

}
 

 

public Mp3Client(string endpointConfigurationName) : 

base(endpointConfigurationName) 

{ 

}
 

 

public Mp3Client(string endpointConfigurationName, string remoteAddress) : 

base(endpointConfigurationName, remoteAddress) 

{ 

}
 

 

public Mp3Client(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 

base(endpointConfigurationName, remoteAddress) 

{ 

}
 

 

public Mp3Client(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 

base(binding, remoteAddress) 

{ 

}
 

 

public void PlaySound(string soundFile) 

{ 

base.Channel.PlaySound(soundFile); 

}
 

}
 

 

[System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.ServiceModel", "3.0.0.0")] 

[System.ServiceModel.ServiceContractAttribute(ConfigurationName
="IRecorder")] 

public interface IRecorder 

{ 

 

[System.ServiceModel.OperationContractAttribute(Action
="http://tempuri.org/IRecorder/Record", ReplyAction="http://tempuri.org/IRecorder/RecordResponse")] 

void Record(); 

}
 

 

[System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.ServiceModel", "3.0.0.0")] 

public interface IRecorderChannel : IRecorder, System.ServiceModel.IClientChannel 

{ 

}
 

 

[System.Diagnostics.DebuggerStepThroughAttribute()] 

[System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.ServiceModel", "3.0.0.0")] 

public partial class RecorderClient : System.ServiceModel.ClientBase<IRecorder>, IRecorder 

{ 

 

public RecorderClient() 

{ 

}
 

 

public RecorderClient(string endpointConfigurationName) : 

base(endpointConfigurationName) 

{ 

}
 

 

public RecorderClient(string endpointConfigurationName, string remoteAddress) : 

base(endpointConfigurationName, remoteAddress) 

{ 

}
 

 

public RecorderClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 

base(endpointConfigurationName, remoteAddress) 

{ 

}
 

 

public RecorderClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 

base(binding, remoteAddress) 

{ 

}
 

 

public void Record() 

{ 

base.Channel.Record(); 

}
 

}
 

 

[System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.ServiceModel", "3.0.0.0")] 

[System.ServiceModel.ServiceContractAttribute(ConfigurationName
="ITelephone")] 

public interface ITelephone 

{ 

 

[System.ServiceModel.OperationContractAttribute(Action
="http://tempuri.org/IMp3/PlaySound", ReplyAction="http://tempuri.org/IMp3/PlaySoundResponse")] 

void PlaySound(string soundFile); 

 

[System.ServiceModel.OperationContractAttribute(Action
="http://tempuri.org/IRecorder/Record", ReplyAction="http://tempuri.org/IRecorder/RecordResponse")] 

void Record(); 

 

[System.ServiceModel.OperationContractAttribute(Action
="http://tempuri.org/ITelephone/Call", ReplyAction="http://tempuri.org/ITelephone/CallResponse")] 

void Call(string to); 

 

[System.ServiceModel.OperationContractAttribute(Action
="http://tempuri.org/ITelephone/Pickup", ReplyAction="http://tempuri.org/ITelephone/PickupResponse")] 

void Pickup(string from); 

 

[System.ServiceModel.OperationContractAttribute(Action
="http://tempuri.org/ITelephone/ReceiveMessage", ReplyAction="http://tempuri.org/ITelephone/ReceiveMessageResponse")] 

void ReceiveMessage(string from); 

 

[System.ServiceModel.OperationContractAttribute(Action
="http://tempuri.org/ITelephone/SendMessage", ReplyAction="http://tempuri.org/ITelephone/SendMessageResponse")] 

void SendMessage(string to); 

}
 

 

[System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.ServiceModel", "3.0.0.0")] 

public interface ITelephoneChannel : ITelephone, System.ServiceModel.IClientChannel 

{ 

}
 

 

[System.Diagnostics.DebuggerStepThroughAttribute()] 

[System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.ServiceModel", "3.0.0.0")] 

public partial class TelephoneClient : System.ServiceModel.ClientBase<ITelephone>, ITelephone 

{ 

 

public TelephoneClient() 

{ 

}
 

 

public TelephoneClient(string endpointConfigurationName) : 

base(endpointConfigurationName)