服务定位器模式(C++实现)

原文链接: An Implementation of the Service Locator Pattern in C++

Service Locator 模式背后的基本思想是:有一个对象(即服务定位器)知道如何获得一个应用程序所需的所有服务。也就是说,在我们的例子中,服务定位器应该有一个方法,用于获得一个我们所需要的服务对象实例。从而将客户端代码和实际的实现代码解耦,用户可以在相同的接口上注册不同的实现,从而可以不改变使用的代码就能改变实现的功能。我们也可以借助IOC的思想,利用XML配置文件来配置服务定位器可以定位的具体服务对象。

打个不恰当的比分,就好比你是大爷,服务定位器是你手下的跟班,你要办什么事情,只管吩咐他找人做,至于他给你找谁就不用管了,只管下命令就是,反正办事的奴才能提供服务的接口都一样。而这些奴才平时找活干的时候都是到你跟班那先登记拿号,由他负责按号叫人,分配工作。

作者在文中依然以Martin Fowler的这篇经典文章《Inversion of Control Containers and the Dependency Injection pattern》的例子来进行说明。这个例子的功能就是获取指定导演执导的所有影片,要考虑的就是两个对象之间的耦合关系,MovieLister对象和MovieFinder对象,前者需要让后者去负责寻找指定导演的影片。Martin Fowler在他的文章中讨论了两种解耦合的方法,一个是依赖注入,一个是服务定位器模式。

#include < memory >
#include
< string >
#include
< map >

using namespace std;

namespace sl
{
structinterface_t
{
virtual~interface_t(){}
}
;

template
<classT>
classmember_t
{
public:
staticinterface_t*create()
{
returnnewT();
}

}
;
classservicelocator_t
{
public:
typedefmap
<string,interface_t*(*)()>_classes_t;
_classes_t_classes;
typedefmap
<string,interface_t*>_singletons_t;
_singletons_t_singletons;
~servicelocator_t()
{
for(_singletons_t::iteratorit=_singletons.begin();it!=_singletons.end();it++)
deleteit
->second;
}

template
<classT>
voidregister_class(conststring&id)
{//注册
_classes.insert(make_pair(id,T::create));
}

//Getsatransientinstance:eachcallcreatesandreturnsareferencetoanewobject
template<classT>
auto_ptr
<T>get_new_instance(conststring&id)
{
_classes_t::iteratorfound
=_classes.find(id);//在map中寻找
if(found!=_classes.end())
returnauto_ptr<T>(dynamic_cast<T*>(found->second()));
throwruntime_error("invalidid:"+id);
}

template
<classT>
T
*get_single_instance(conststring&id)
{//返回单例实例
_singletons_t::iteratorfound_singleton=_singletons.find(id);
//先在单例集合中寻找,
if(found_singleton!=_singletons.end())
returndynamic_cast<T*>(found_singleton->second);
//若单例集合中没有,则在注册集合中找,找到则放入单例集合中
_classes_t::iteratorfound_class=_classes.find(id);
if(found_class!=_classes.end())
{
T
*obj(dynamic_cast<T*>(found_class->second()));
_singletons.insert(make_pair(id,obj));
returnobj;
}

throwruntime_error("invalidid:"+id);
}

}
;
}
;

那么要实现服务定位器模式,利用上面这段辅助代码,我们可以这样实现:
首先让工作者接口从sl::interface_t接口继承下来:

struct MovieFinder:sl::interface_t
{
virtualvector<Movie>findAll()=0;
}
;

然后将具体工作者类从sl::member_tMovieFinder继承下来

class ColonDelimitedMovieFinder: public sl::member_t < ColonDelimitedMovieFinder > public MovieFinder
{
}


创建一个服务定位器实例

sl::servicelocator_tlocator;

在服务器定位器中注册工作者类,给它一个唯一的名称来作为标识

locator.register_class < ColonDelimitedMovieFinder > ( " finder " );

现在可以用服务器定位器来寻找你所需要的服务者了:

MovieFinder * finder = locator.get_single_instance < MovieFinder > ( " finder " );

当然你也可以这样找服务者对象:

auto_ptr < MovieFinder > finder = locator.get_new_instance < MovieFinder > ( " finder " );

Service Locator vs. Dependency Injection

首先,我们面临Service Locator Dependency Injection 之间的选择。应该注意,尽管我们前面那个简单的例子不足以表现出来,实际上这两个模式都提供了基本的解耦合能力——无论使用哪个模式,应用程序代码都不依赖于服务接口的具体实现。两者之间最重要的区别在于:这个具体实现以什么方式提供给应用程序代码。使用Service Locator 模式时,应用程序代码直接向服务定位器发送一个消息,明确要求服务的实现;使用Dependency Injection 模式时,应用程序代码不发出显式的请求,服务的实现自然会出现在应用程序代码中,这也就是所谓控制反转

控制反转是框架的共同特征,但它也要求你付出一定的代价:它会增加理解的难度,并且给调试带来一定的困难。所以,整体来说,除非必要,否则我会尽量避免使用它。这并不意味着控制反转不好,只是我认为在很多时候使用一个更为直观的方案(例如Service Locator 模式)会比较合适。

一个关键的区别在于:使用Service Locator 模式时,服务的使用者必须依赖于服务定位器。定位器可以隐藏使用者对服务具体实现的依赖,但你必须首先看到定位器本身。所以,问题的答案就很明朗了:选择Service Locator 还是Dependency Injection,取决于对定位器的依赖是否会给你带来麻烦。

Dependency Injection 模式可以帮助你看清组件之间的依赖关系:你只需观察依赖注入的机制(例如构造子),就可以掌握整个依赖关系。而使用Service Locator 模式时,你就必须在源代码中到处搜索对服务定位器的调用。具备全文检索能力的IDE 可以略微简化这一工作,但还是不如直接观察构造子或者设值方法来得轻松。

这个选择主要取决于服务使用者的性质。如果你的应用程序中有很多不同的类要使用一个服务,那么应用程序代码对服务定位器的依赖就不是什么大问题。在前面的例子中,我要把MovieLister 类交给朋友去用,这种情况下使用服务定位器就很好:我的朋友们只需要对定位器做一点配置(通过配置文件或者某些配置性的代码),使其提供合适的服务实现就可以了。在这种情况下,我看不出Dependency Injection 模式提供的控制反转有什么吸引人的地方。但是,如果把MovieLister 看作一个组件,要将它提供给别人写的应用程序去使用,情况就不同了。在这种时候,我无法预测使用者会使用什么样的服务定位器API,每个使用者都可能有自己的服务定位器,而且彼此之间无法兼容。一种解决办法是为每项服务提供单独的接口,使用者可以编写一个适配器,让我的接口与他们的服务定位器相配合。但即便如此,我仍然需要到第一个服务定位器中寻找我规定的接口。而且一旦用上了适配器,服务定位器所提供的简单性就被大大削弱了。

另一方面,如果使用Dependency Injection 模式,组件与注入器之间不会有依赖关系,因此组件无法从注入器那里获得更多的服务,只能获得配置信息中所提供的那些。这也是Dependency Injection 模式的局限性之一。

人们倾向于使用Dependency Injection 模式的一个常见理由是:它简化了测试工作。这里的关键是:出于测试的需要,你必须能够轻松地在真实的服务实现供测试用的组件之间切换。但是,如果单从这个角度来考虑,Dependency Injection 模式和Service Locator

模式其实并没有太大区别:两者都能够很好地支持组件的插入。之所以很多人有Dependency Injection 模式更利于测试的印象,我猜是因为他们并没有努力保证服务定位器的可替换性。这正是持续测试起作用的地方:如果你不能轻松地用一些组件将一个服务架起来以便测试,这就意味着你的设计出现了严重的问题。

当然,如果组件环境具有非常强的侵略性(就像EJB 框架那样),测试的问题会更加严重。我的观点是:应该尽量减少这类框架对应用程序代码的影响,特别是不要做任何可能使编辑-执行的循环变慢的事情。用插件(plugin)机制取代重量级组件会对测试过程有很大帮助,这正是测试驱动开发(Test Driven DevelopmentTDD)之类实践的关键所在。

所以,主要的问题在于:代码的作者是否希望自己编写的组件能够脱离自己的控制、被使用在另一个应用程序中。如果答案是肯定的,那么他就不能对服务定位器做任何假设——哪怕最小的假设也会给使用者带来麻烦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值