依赖倒置和案例
1. 依赖倒置原则,为什么叫好莱坞原则?
依赖倒置原则(Dependence Inversion Principle):
1、高层模块不应该依赖底层模块,二者都应该依赖抽象。
2、抽象不应该依赖细节,细节应该依赖抽象。
3、依赖倒置的中心思想是面向接口编程。
4、依赖倒置原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础搭建的架构要稳定的多。
5、使用接口或抽象类的目的是指定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类来完成。
好莱坞原则(Hollywood Principle):别打电话给我们,有事我会打电话给你。好莱坞原则是用在系统的高层组件与底层组件之间。高层组件不应该直接调用底层组件,而是从容器获取。
2. 依赖倒置原则的应用
实现 Web Service 依赖倒置
分析
相信在实践设计模式的过程中,开发人员已经对依赖倒置的概念有了深刻的体验,“不依赖于具体实现,而是依赖于抽象”,整理 SOA 环境下的 Web Service 一样需要借鉴这个概念,笔者将之称为“Web Service 依赖倒置”。大概逻辑结构变成如下:
图2:概要Web Service依赖倒置后的逻辑关系
但Web Service本身接口是“平的”,没有办法继承,只有用OO语言把它进行包装之后才可以成为对应的类,这时候才能有所谓的“继承”或“接口实现”;所谓“抽象”既可能是接口也可能是抽象类(当然,也可以考虑用实体基类),所以在处理ConcreteWebService与抽象Web Service的时候也有两种方式:
l 通过继承的
l 通过单继承+多接口组合的
笔者更倾向于后者,因为通过组合可以不断扩展。同时考虑到Web Service使用往往在一个分布式的环境中,因此参考RPC中常用的叫法,增加了一一个Stub(用接口IServiceX表示)和Proxy。修改后依赖倒置的关系如下:
图3:分布式环境下多组合服务接口实现的Web Service依赖倒置
实现示例
1、对业务数据建模(XSD):
假设业务对象为报价信息,报价分为报价头和明细(1:0..n),因此结构如下:
图4:报价信息的XSD
XSD
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
xmlns="http://www.visionlogic.com/trade"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.visionlogic.com/trade"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:element name="Quote">
<xs:annotation>
<xs:documentation>Comment describing your root element</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element ref="QuoteItem" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="Id" type="xs:string" use="required"/>
<xs:attribute name="Company" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="QuoteItem">
<xs:complexType>
<xs:attribute name="ProductId" type="xs:integer" use="required"/>
<xs:attribute name="Price" type="xs:double" use="required"/>
<xs:attribute name="QuantitiveInStock" type="xs:double"/>
</xs:complexType>
</xs:element>
</xs:schema>
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)
<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://www.visionlogic.com/trade" xmlns:s1="http://www.visoinlogic.com/trade" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" targetNamespace="http://www.visionlogic.com/trade" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<wsdl:types>
<s:schema elementFormDefault="qualified" targetNamespace="http://www.visionlogic.com/trade">
<s:import namespace="http://www.visoinlogic.com/trade" />
<s:element name="GetQuote">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="id" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetQuoteResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" ref="s1:Quote" />
</s:sequence>
</s:complexType>
</s:element>
… …
<wsdl:service name="QuoteService">
<wsdl:port name="QuoteServiceSoap" binding="tns:QuoteServiceSoap">
<soap:address location="http://localhost:2401/QuoteServiceBase.asmx" />
</wsdl:port>
<wsdl:port name="QuoteServiceSoap12" binding="tns:QuoteServiceSoap12">
<soap12:address location="http://localhost:2401/QuoteServiceBase.asmx" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
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
{
/// <summary>
///具体的报价Web Service功能实现
/// </summary>
[WebService(Namespace = "http://www.visionlogic.com/trade")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class IntranetQuoteService : WebService, IQuoteServiceSoap
{
/// <summary>
///实现抽象的Web Service调用
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[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
{
/// <summary>
/// Web Service的客户端Proxy
/// </summary>
[WebServiceBindingAttribute(
Name="QuoteServiceSoap",
Namespace="http://www.visionlogic.com/trade")]
public class QuoteService : SoapHttpClientProtocol
{
/// <summary>
///借助SOAP消息调用Web Service服务端
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[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
{
/// <summary>
///测试用客户程序
/// </summary>
[TestClass]
public class Client
{
/// <summary>
///为了简化,这里在客户程序中直接定义了具体报价Web Service的Uri.
///实际开发中该信息应该作为服务端的一个配置项登记在Directory之中,
///客户程序仅仅通过抽象的服务逻辑名称从Directory中获得。)
/// </summary>
[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<string>("quote:2007-07-15", quote.Id);
Assert.AreEqual<string>("deluxe", quote.Company);
Assert.AreEqual<int>(2, quote.QuoteItem.Length);
Assert.IsNotNull(quote.QuoteItem[0]);
}
}
}
注:为了使用方便,本系列所有示例都没有直接采用IIS作为Web Server宿主,而是采用Visual Studio.Net自带的临时服务进程,因此WSDL和Proxy的使用上,相关端口可能会变化。
进一步改进
上面的示例在客户端处理上不算成功,因为它需要客户程序提供ConcreteService的Uri,怎么改进呢?回忆我们通常对连接串的处置办法:
l 应用逻辑使用一个逻辑的数据库名称,通过一个数据访问框架调用逻辑的数据库。
l 数据访问框架中有一个类似ConnectionManager的机制,负责把逻辑的数据库连接名翻译成实际的连接串。
对上面那个Web Service示例的也如法炮制,增加一个逻辑的Directory机制,实际工程中这个Directory可能就是个UDDI服务,不过这里定义了一个精简对象。
图5:为客户程序增加服务Uri管理目录机制
实现如下
C# IServiceDirectory
using System;
namespace Test.Client
{
/// <summary>
///抽象的服务目录接口
/// </summary>
public interface IServiceDirectory
{
/// <summary>
///通过索引器实现按名称或取实际服务Uri的机制。
///为了约束客户程序对服务目录的使用,仅提供一个readonly的访问机制。
/// </summary>
/// <param name="name">逻辑的服务名称</param>
/// <returns>实际服务实体的Uri </returns>
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
{
/// <summary>
///保存逻辑服务名称与具体Uri对应关系的目录字典。
/// </summary>
private static IDictionary<string, string> dictionary = null;
/// <summary>
///静态构造的过程中,通过访问配置,获取对应关系。
/// </summary>
static LocalServiceDirectory()
{
NameValueCollection appSettings = ConfigurationManager.AppSettings;
if ((appSettings == null) || (appSettings.Count <= 0)) return;
dictionary = new Dictionary<string, string>();
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
{
/// <summary>
///为了隔离客户程序对实际DirectoryService类型的以来,引入的服务目录工厂。
/// </summary>
public static class DirectoryServiceFactory
{
/// <summary>
///工厂方法。
///世纪项目中,实体ServiceDirectory类型可能运行于远端服务器上,
///或者就是UDDI服务,获取IServiceDirectory过程可能还需要借助代理程序完成。
/// </summary>
/// <returns></returns>
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"];
… …
}
}
}
CacheLoader
如果有合理的默认方法来加载或计算与键关联的值。
LoadingCache是附带CacheLoader构建而成的缓存实现。创建自己的CacheLoader通常只需要简单地实现V load(K key) throws Exception方法。
从LoadingCache查询的正规方式是使用get(K)方法。这个方法要么返回已经缓存的值,要么使用CacheLoader向缓存原子地加载新值。由于CacheLoader可能抛出异常,LoadingCache.get(K)也声明为抛出ExecutionException异常。
CacheBuilder
缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。
主要采用builder的模式,CacheBuilder的每一个方法都返回这个CacheBuilder知道build方法的调用。
注意build方法有重载,带有参数的为构建一个具有数据加载功能的缓存,不带参数的构建一个没有数据加载功能的缓存。
LocalManualCache
作为LocalCache的一个内部类,在构造方法里面会把LocalCache类型的变量传入,并且调用方法时都直接或者间接调用LocalCache里面的方法。
作者:Acamy丶
链接:https://www.jianshu.com/p/38bd5f1cf2f2
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
版权声明: 本文为 InfoQ 作者【王锟】的原创文章。
原文链接:【http://xie.infoq.cn/article/75d94de8ce2e9b7f299958176】。文章转载请联系作者。
评论