服务定位器模式(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
{
    
struct interface_t
    
{
        
virtual ~interface_t() {}
    }
;

    template
<class T>
    
class member_t
    
{
    
public:
        
static interface_t* create()
        
{
            
return new T();
        }

    }
;
    
class servicelocator_t
    
{
    
public:
        typedef map
<string, interface_t*(*)()>    _classes_t;
        _classes_t                                _classes;
        typedef map
<string, interface_t*>        _singletons_t;
        _singletons_t                            _singletons;
        
~servicelocator_t()
        
{
            
for ( _singletons_t::iterator it = _singletons.begin(); it != _singletons.end(); it++ )
                delete it
->second;
        }

        template
<class T>
        
void register_class( const string& id )
        
{//注册
            _classes.insert( make_pair( id, T::create) );
        }

        
// Gets a transient instance: each call creates and returns a reference to a new object
        template<class T>
        auto_ptr
<T> get_new_instance( const string& id )
        
{
            _classes_t::iterator found 
= _classes.find(id);//在map中寻找
            if ( found != _classes.end() )
                
return auto_ptr<T>( dynamic_cast<T*>( found->second() ) );
            
throw runtime_error( "invalid id: " + id );
        }

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

            
throw runtime_error( "invalid id: " + id );
        }

    }
;
}
;

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

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

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

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

}


 创建一个服务定位器实例

sl::servicelocator_t locator;

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

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、付费专栏及课程。

余额充值