热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

依赖倒置原则(DIP)转自:http://www.infoq.com/cn/articles/Implements-DIP-of-Web-Service

实现WebService依赖倒置作者译者王翔发布于2007年8月1日下午10时49分社区.NET主题设计,Web服务标签XML问题的提出作为面向对象设计的一个基本原则,依赖倒置原则(DIP

实现Web Service依赖倒置

作者 译者 王翔 发布于 2007年8月1日 下午10时49分

社区
.NET
主题
设计,
Web服务
标签
XML

问题的提出

作为面向对象设计的一个基本原则,依赖倒置原则(DIP)在降低模块间耦合度方面有很好的指导意义,他的基本要求和示意图如下:

相关厂商内容

QClub:OpenSocial规范、实现现状与展望(8.3 北京)

“高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于细节。细节应该依赖于抽象。”

图1:直接依赖(I)和依赖关系倒置(II)

这么做有什么优势呢?

  • 降低Client与ConcreteService的耦合度。
  • ConcreteService可以自主的变化,只要符合IService,就可以继续被Client使用。即这种变化对Client透明。
  • 应用框架可以Client的上下文为他物色一个合适的ConcreteService,动态构造、动态绑定、运行时动态调用。

在单应用时代,基于接口的开发指导了我们养成这种习惯,但是到了SOA环境下,通常的Web Service开发情况又是怎么样呢?

  1. 客户程序需要调用某个Web Service,获得它的WSDL和相关数据结构的XSD。
  2. 然后客户程序调用这个Web Service,一般情况下如果WSDL不变的话,可以一直使用该Web Service,即便那个Web Service后面的实现平台发生变化,但因为绑定关系没有变化,所以客户程序不需要任何修改(偶尔因为版本问题,有可能会进行适应性调整)。
  3. 如果发现有新的相同服务接口的Service Provider做的不错的化或者把原有Web Service做迁移的话,那就需要重新更新WSDL,编译新的Web Service Client Proxy类,有可能客户程序也要重新编译。

Web Service很好地隔绝了服务定义与服务实现两者的关系,同时它也把可能的备选功能提供者从内部一下子推到整个互联网环境下,怎么让客户程序透明的适应众多可选服务就成了一个挑战。

怎么办?老办法——抽象

实现Web Service依赖倒置

分析

相信在实践设计模式的过程中,开发人员已经对依赖倒置的概念有了深刻的体验,“不依赖于具体实现,而是依赖于抽象”,整理SOA环境下的Web Service一样需要借鉴这个概念,笔者将之称为“Web Service依赖倒置”。大概逻辑结构变成如下:

图2:概要Web Service依赖倒置后的逻辑关系

但Web Service本身接口是“平的”,没有办法继承,只有用OO语言把它进行包装之后才可以成为对应的类,这时候才能有所谓的“继承”或“接口实现”;所谓“抽象”既可能是接口也可能是抽象类(当然,也可以考虑用实体基类),所以在处理ConcreteWebService与抽象Web Service的时候也有两种方式:

  • 通过继承的
  • 通过单继承+多接口组合的

笔者更倾向于后者,因为通过组合可以不断扩展。同时考虑到Web Service使用往往在一个分布式的环境中,因此参考RPC中常用的叫法,增加了一一个Stub(用接口IServiceX表示)和Proxy。修改后依赖倒置的关系如下:

图3:分布式环境下多组合服务接口实现的Web Service依赖倒置

实现示例

1、对业务数据建模(XSD):

假设业务对象为报价信息,报价分为报价头和明细(1:0..n),因此结构如下:

图4:报价信息的XSD

XSD

  xmlns="http://www.visionlogic.com/trade" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  targetNamespace="http://www.visionlogic.com/trade" 
  elementFormDefault="qualified" 
  attributeFormDefault="unqualified">
    
        
            Comment describing your root element
        

        
            
                
            

            
            
        

    

    
        
            
            
            
        

    

2、完成XSD与对象实体的映射:(XSD to Object)

Command

通过Visual Studio.Net自带的Xsd.exe进行如下操作。


xsd Quote.xsd /c /n:DemoService

这样就生成了结构大概如下的对应的报价实体类:

C#
using System;
using System.Xml.Serialization;
namespace DemoService
{
    [System.SerializableAttribute()]
    [XmlTypeAttribute(AnOnymousType= true, Namespace = "http://www.visionlogic.com/trade")]
    [XmlRootAttribute(Namespace = "http://www.visionlogic.com/trade", IsNullable = false)]
    public partial class Quote
    {
        private QuoteItem[] quoteItemField;
        private string idField;
        private string companyField;
        [XmlElementAttribute("QuoteItem")]
        public QuoteItem[] QuoteItem
        {
            get { return this.quoteItemField; }
            set { this.quoteItemField = value; }
        }
        [XmlAttributeAttribute()]
        public string Id
        {
            get { return this.idField; }
            set { this.idField = value; }
        }
        [XmlAttributeAttribute()]
        public string Company
        {
            get { return this.companyField; }
            set { this.companyField = value; }
        }
    }

    [SerializableAttribute()]
    [XmlTypeAttribute(AnOnymousType= true, Namespace = "http://www.visionlogic.com/trade")]
    [XmlRootAttribute(Namespace = "http://www.visionlogic.com/trade", IsNullable = false)]
    public partial class QuoteItem
{
… …
    }
}

3、接着,完成抽象的Web Service定义(optional):

该步骤的目的是获取wsdl定义。这里笔者为了省事,用Visual Studio.Net自动生成,所以写了个抽象的Web Service类,实际开发中完全可以独立编写wsdl文件。

C#
using System.Web.Services;
using System.Xml.Serialization;
namespace DemoService
{
    [WebService(Name="QuoteService", Namespace="http://www.visionlogic.com/trade")]
    public abstract class QuoteServiceBase : WebService
    {
        [WebMethod()]
        [return:XmlElement("Quote", Namespace="http://www.visoinlogic.com/trade")]
        public abstract Quote GetQuote(string id);
    }
}
WSDL (Quote.wsdl)


 
   
     
     
       
         
           
         

       

     

     
       
         
           
         

       

     

… …
 
   
     
   

   
     
   

 

4、生成Web Service接口类型:

Command

通过Visual Studio.Net自带的Wsdl.exe进行如下操作。


wsdl /n:DemoService /serverinterface /o:IQuoteStub.cs Quote.wsdl Quote.xsd

这样就生成了报价Web Service的抽象接口:

C#
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Services.Description;
using System.Xml.Serialization;
namespace DemoService 
{
    [WebServiceBindingAttribute(
        Name = "QuoteServiceSoap", Namespace = "http://www.visionlogic.com/trade")]
    public interface IQuoteServiceSoap
    {
        [WebMethodAttribute()]
        [SoapDocumentMethodAttribute(
            "http://www.visionlogic.com/trade/GetQuote", 
            RequestNamespace = "http://www.visionlogic.com/trade", 
            RespOnseNamespace= "http://www.visionlogic.com/trade", 
            Use = SoapBindingUse.Literal, 
            ParameterStyle = SoapParameterStyle.Wrapped)]
        [return: XmlElementAttribute("Quote", 
            Namespace = "http://www.visoinlogic.com/trade")]
        Quote GetQuote(string id);
    }
}

5、生成具体的报价Web Service:

为了示例的方便,IntranetQuoteService自己“手捏”了一票测试报价数据,至此服务端Web Service工作基本完成,如果需要使用UDDI则还需要把这个具体服务publish出来。

C#
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
namespace DemoService
{
    ///
    /// 具体的报价Web Service 功能实现
    ///

    [WebService(Namespace = "http://www.visionlogic.com/trade")]
    [WebServiceBinding(COnformsTo= WsiProfiles.BasicProfile1_1)]
    public class IntranetQuoteService : WebService, IQuoteServiceSoap
    {
        ///
        /// 实现抽象的Web Service调用
        ///

        ///
        ///
        [WebMethod]
        public Quote GetQuote(string id)
        {
            #region "手捏"出来的测试数据
            Quote quote = new Quote();
            quote.Id = id;
            quote.Company = "deluxe";

            QuoteItem[] items = new QuoteItem[2];
            items[0] = new QuoteItem();
            items[0].QuantitiveInStockSpecified = true;
            items[0].ProductId = "Note Bulletin";
            items[0].Price = 220;
            items[0].QuantitiveInStock = 10;
            items[1] = new QuoteItem();
            items[1].QuantitiveInStockSpecified = true;
            items[1].ProductId = "Pen";
            items[1].Price = 3.4;
            items[1].QuantitiveInStock = 3000;
            quote.QuoteItem = items;
            #endregion

            return quote;
        }
    }
}

6、生成客户端Proxy:

Command

通过Visual Studio.Net自带的Wsdl.exe进行如下操作。


wsdl /n:Test.Client /o:QuoteProxy.cs Quote.wsdl Quote.xsd

这样就生成了报价Web Service的客户端Proxy,他仅通过最初抽象Web Service的WSDL调用服务端Web Service。实际运行过程中,它并不了解真正使用的时候是由哪个服务提供WSDL中声明到的“GetQuote”方法。

C#
using System.Web.Services;
using System.Threading;
using System.Web.Services.Protocols;
using System.Web.Services.Description;
using System.Xml.Serialization;
using DemoService;
namespace Test.Client
{
    ///
    /// Web Service 的客户端 Proxy
    ///

    [WebServiceBindingAttribute(
        Name="QuoteServiceSoap", 
        Namespace="http://www.visionlogic.com/trade")]
    public class QuoteService : SoapHttpClientProtocol
    {   
        ///
        /// 借助 SOAP 消息调用 Web Service 服务端
        ///

        ///
        ///
        [SoapDocumentMethodAttribute(
            "http://www.visionlogic.com/trade/GetQuote", 
            RequestNamespace="http://www.visionlogic.com/trade", 
            RespOnseNamespace="http://www.visionlogic.com/trade", 
            Use=SoapBindingUse.Literal, 
            ParameterStyle=SoapParameterStyle.Wrapped)]
        [return: XmlElementAttribute("Quote", 
            Namespace="http://www.visoinlogic.com/trade")]
        public Quote GetQuote(string id) 
        {
            object[] results = this.Invoke("GetQuote", new object[] {id});
            return ((Quote)(results[0]));
        }
    }
}

7、客户程序:

最后,通过单元测试工具检查的客户程序如下:

C#
using System;
using DemoService;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Test.Client
{
    ///
    /// 测试用客户程序
    ///

    [TestClass]
    public class Client
    {
        ///
        /// 为了简化,这里在客户程序中直接定义了具体报价Web Service的Uri.
        /// 实际开发中该信息应该作为服务端的一个配置项登记在Directory之中,
        /// 客户程序仅仅通过抽象的服务逻辑名称从Directory中获得。)
        ///

        [TestMethod]
        public void Test()
        {
            QuoteService service = new QuoteService();
            service.Url = "http://localhost:2401/IntranetQuoteService.asmx";
            Quote quote = service.GetQuote("quote:2007-07-15");
            Assert.AreEqual("quote:2007-07-15", quote.Id);
            Assert.AreEqual("deluxe", quote.Company);
            Assert.AreEqual(2, quote.QuoteItem.Length);
            Assert.IsNotNull(quote.QuoteItem[0]);
        }
    }
}

注:为了使用方便,本系列所有示例都没有直接采用IIS作为Web Server宿主,而是采用Visual Studio.Net自带的临时服务进程,因此WSDL和Proxy的使用上,相关端口可能会变化。

进一步改进

上面的示例在客户端处理上不算成功,因为它需要客户程序提供ConcreteService的Uri,怎么改进呢?回忆我们通常对连接串的处置办法:

  • 应用逻辑使用一个逻辑的数据库名称,通过一个数据访问框架调用逻辑的数据库。
  • 数据访问框架中有一个类似ConnectionManager的机制,负责把逻辑的数据库连接名翻译成实际的连接串。

对上面那个Web Service示例的也如法炮制,增加一个逻辑的Directory机制,实际工程中这个Directory可能就是个UDDI服务,不过这里定义了一个精简对象。

图5:为客户程序增加服务Uri管理目录机制

实现如下

C# IServiceDirectory
using System;
namespace Test.Client
{
    ///
    /// 抽象的服务目录接口
    ///

    public interface IServiceDirectory
    {
        ///
        /// 通过索引器实现按名称或取实际服务Uri 的机制。
        /// 为了约束客户程序对服务目录的使用,仅提供一个readonly 的访问机制。
        ///

        /// 逻辑的服务名称
        /// 实际服务实体的Uri
        string this[string name] { get;}
    }
}
C# LocalServiceDirectory
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
namespace Test.Client
{
    class LocalServiceDirectory : IServiceDirectory
    {
        ///
        /// 保存逻辑服务名称与具体Uri 对应关系的目录字典。
        ///

        private static IDictionary dictiOnary= null;

        ///
        /// 静态构造的过程中,通过访问配置,获取对应关系。
        ///

        static LocalServiceDirectory()
        {
            NameValueCollection appSettings = ConfigurationManager.AppSettings;
            if ((appSettings == null) || (appSettings.Count <= 0)) return;
            dictiOnary= new Dictionary();
            foreach (string name in appSettings.Keys)
                dictionary.Add(name, appSettings[name]);
        }

        public string this[string name]
        {
            get
            {
                string uri;
                if (!dictionary.TryGetValue(name, out uri))
                    return string.Empty;
                else
                    return uri;
            }
        }
    }
}
C# DirectoryServiceFactory
using System;
namespace Test.Client
{
    ///
    /// 为了隔离客户程序对实际DirectoryService 类型的以来,引入的服务目录工厂。
    ///

    public static class DirectoryServiceFactory
    {
        ///
        /// 工厂方法。
        /// 世纪项目中,实体ServiceDirectory 类型可能运行于远端服务器上,
        /// 或者就是UDDI服务,获取IServiceDirectory 过程可能还需要借助代理程序完成。
        ///

        ///
        public static IServiceDirectory Create()
        {
            return new LocalServiceDirectory();
        }
    }
}
C# 修改后的客户程序
using System;
using DemoService;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Test.Client
{
    [TestClass]
    public class Client
    {
        [TestMethod]
        public void Test()
        {
            QuoteService service = new QuoteService();
            service.Url = DirectoryServiceFactory.Create()["QuoteService"];
  … …
        }
    }
}

进一步讨论

在有效的隔离了实体Web Service与抽象Web Service的关系后,之前设计模式、架构模式中的那些套路就又有了用武之地,比如Observer、Adapter、Factory、Blackboard、MVC… …,甚至于Visitor这中双因素以来的模式也可以套用,只不过原来的消息变成了XML SOAP、对象实体变成了XSD定义下的各种XML,至于UI上能看到的东西还有需要转换的信息由XSL完成即可。


作者简介: 王翔,软件架构师,主要方向为XML技术、.NET平台开发与集成、领域设计和公钥基础环境应用。近年主要参与数据交换系统、自订制业务领域语言平台项目和信息安全类项目,工余时间喜欢旅游、写作、解趣味数学问题和烹饪。
加入书签鲜果+,digg+,reddit+,del.icio.us+,dzone+
var replyEnabled=true; var forumID=1; var threadID=2575; var previewText='预览'; var pleaseWait='请稍候……'; var reply='回复'; var postMessage='发送消息'; var errorSubject='请输入主题。'; var errorBody='您不允许发表无内容的消息。请输入您的消息并重试。'; var cancel='取消'; var goBackOrEdit='返回/编辑'; var re='Re:'; var lastMessage=0; var stopWatchText='取消对此讨论的关注'; var startWatchText='关注此讨论'; var descending='false'; var ctxPath= ''; var postAddress= ctxPath + '/forum/post!post.action?language=' + 'zh'; var postAddWatches= ctxPath + '/forum/watches!add.action'; var postRemoveWatches= ctxPath + '/forum/watches!remove.action'; var loggedIn=false;

4 条回复

回复

或许应该更进一步的抽象 发表人 Anders Lin 发表于 2007年8月2日 下午11时52分 Event.observe('tooltip_9031', 'mouseover', initializeTooltip); Event.observe('tooltip_9031', 'mouseout', deintializeTooltip); Re: 或许应该更进一步的抽象 发表人 hello hello 发表于 2007年8月3日 上午5时36分 Event.observe('tooltip_9032', 'mouseover', initializeTooltip); Event.observe('tooltip_9032', 'mouseout', deintializeTooltip); Re: 或许应该更进一步的抽象 发表人 hello hello 发表于 2007年8月3日 上午5时36分 Event.observe('tooltip_9033', 'mouseover', initializeTooltip); Event.observe('tooltip_9033', 'mouseout', deintializeTooltip); Re: 或许应该更进一步的抽象 发表人 hello hello 发表于 2007年8月3日 上午5时38分 Event.observe('tooltip_9034', 'mouseover', initializeTooltip); Event.observe('tooltip_9034', 'mouseout', deintializeTooltip); 按日期倒序排列
  1. 返回顶部

    或许应该更进一步的抽象

    2007年8月2日 下午11时52分 发表人 Anders Lin

    目前的抽象仅是隔离Web Service的抽象和实现,或许可以更进一步,隔离接口和实现。接口的实现是调用web service还是调用本地,都不是client关心的。


推荐阅读
  • Postman 调试 WebService
    Postman调试WebServiceWebServicePostman设置Headers请求头参数Body请求体传参返回结果WebService天气预报Web服务http:ww ... [详细]
  • 1.一般应用(访问名称空间下方法)usenamespacemx_internal;vartextArea:TextAreaTextArea();textArae.htmlText ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • centos安装Mysql的方法及步骤详解
    本文介绍了centos安装Mysql的两种方式:rpm方式和绿色方式安装,详细介绍了安装所需的软件包以及安装过程中的注意事项,包括检查是否安装成功的方法。通过本文,读者可以了解到在centos系统上如何正确安装Mysql。 ... [详细]
  • 1.WebServicea.定义:WebService是一种跨编程语言和跨操作系统平台的远程调用技术b.三大技术:XMLXSD,SOAP, ... [详细]
  • .NET最新漏洞CVE20178759 POC已公布,大规模攻击即将来临
    图世界范围内感染FinSpy图(即漏洞利用导致下载的间谍程序)前天微软刚补好的漏洞,昨天fireeye发文,然后各平台开始写 ... [详细]
author-avatar
手浪用户2702933404
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有