1. 程式人生 > >服務定位器模式(Service Locator)

服務定位器模式(Service Locator)

1、模式定義

當系統中的元件需要呼叫某一服務來完成特定的任務時,通常最簡單的做法是使用 new 關鍵字來建立該服務的例項,或者通過工廠模式來解耦該元件與服務的具體實現部分,以便通過配置資訊等更為靈活的方式獲得該服務的例項。然而,這些做法都有著各自的弊端:

1)、在元件中直接維護對服務例項的引用,會造成元件與服務之間的關聯依賴,當需要替換服務的具體實現時,不得不修改元件中呼叫服務的部分並重新編譯解決方案;即使採用工廠模式來根據配置資訊動態地獲得服務的例項,也無法針對不同的服務型別向元件提供一個管理服務例項的中心位置;

2)、由於元件與服務之間的這種關聯依賴,使得專案的開發過程受到約束。在實際專案中,開發過程往往是並行的,但又不是完全同步的,比如元件的開發跟其所需要的服務的開發同時進行,但很有可能當元件需要呼叫服務時,服務卻還沒完成開發和單體測試。遇到這種問題時,通常會將元件呼叫服務的部分暫時空缺,待到服務完成開發和單體測試之後,將其整合到元件的程式碼中。但這種做法不僅費時,而且增大了出錯的風險;

3)、針對元件的單體測試變得複雜。每當對元件進行單體測試時,不得不為其配置並執行所需要的服務,而無法使用Service Stub來解決元件與服務之間的依賴;

4)、在元件中可能存在多個地方需要引用服務的例項,在這種情況下,直接建立服務例項的程式碼會散佈到整個程式中,造成一段程式存在多個副本,大大增加維護和排錯成本;

5)、當元件需要呼叫多個服務時,不同服務初始化各自例項的方式又可能存在差異。開發人員不得不瞭解所有服務初始化的API,以便在程式中能夠正確地使用這些服務;

6)、某些服務的初始化過程需要耗費大量資源,因此多次重複地初始化服務會大大增加系統的資源佔用和效能損耗。程式中需要有一個管理服務初始化過程的機制,在統一初始化介面的同時,還需要為程式提供部分快取功能。

要解決以上問題,我們可以在應用程式中引入服務定位器(Service Locator)模式。

服務定位器(Service Locator)模式是一種企業級應用程式體系結構模式,它能夠為應用程式中服務的建立和初始化提供一箇中心位置,並解決了上文中所提到的各種設計和開發問題。

服務定位器模式和依賴注入模式都是控制反轉(IoC)模式的實現。我們在服務定位器中註冊給定介面的服務例項,然後通過介面獲取服務並在應用程式碼中使用而不需要關心其具體實現。我們可以在啟動時配置並注入服務提供者。

如果你瞭解 Laravel 框架,你對這一流程會很熟悉,沒錯,這就是 Laravel 框架的核心機制,我們在服務提供者中繫結介面及其實現,將服務例項註冊到服務容器中,然後在使用時可以通過依賴注入或者通過服務介面/別名獲取服務例項的方式呼叫服務。

2、UML類圖

Service-Locator-Design-Pattern

3、示例程式碼

ServiceLocatorInterface.php

123456789101112131415161718192021222324 <?phpnamespaceDesignPatterns\More\ServiceLocator;interfaceServiceLocatorInterface{/**     * Checks if a service is registered.     *     * @param string $interface     *     * @return bool     */publicfunctionhas($interface);/**     * Gets the service registered for the interface.     *     * @param string $interface     *     * @return mixed     */publicfunctionget($interface);}

ServiceLocator.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 <?phpnamespaceDesignPatterns\More\ServiceLocator;classServiceLocatorimplementsServiceLocatorInterface{/**     * All services.     *     * @var array     */private$services;/**     * The services which have an instance.     *     * @var array     */private$instantiated;/**     * True if a service can be shared.     *     * @var array     */private$shared;publicfunction__construct(){$this->services=array();$this->instantiated=array();$this->shared=array();}/**     * Registers a service with specific interface.     *     * @param string $interface     * @param string|object $service     * @param bool $share     */publicfunctionadd($interface,$service,$share=true){/**         * When you add a service, you should register it         * with its interface or with a string that you can use         * in the future even if you will change the service implementation.         */if(is_object($service)&&$share){$this->instantiated[$interface]=$service;}$this->services[$interface]=(is_object($service)?get_class($service):$service);$this->shared[$interface]=$share;}/**     * Checks if a service is registered.     *     * @param string $interface     *     * @return bool     */publicfunctionhas($interface){return(isset($this->services[$interface])||isset($this->instantiated[$interface]));}/**     * Gets the service registered for the interface.     *     * @param string $interface     *     * @return mixed     */publicfunctionget($interface){// Retrieves the instance if it exists and it is sharedif(isset($this->instantiated[$interface])&&$this->shared[$interface]){return$this->instantiated[$interface];}// otherwise gets the service registered.$service=$this->services[$interface];// You should check if the service class exists and// the class is instantiable.// This example is a simple implementation, but// when you create a service, you can decide// if $service is a factory or a class.// By registering a factory you can create your services// using the DependencyInjection pattern.// ...// Creates the service object$object=new$service();// and saves it if the service must be shared.if($this->shared[$interface]){$this->instantiated[$interface]=$object;}return$object;}}

LogServiceInterface.php

1234567 <?phpnamespaceDesignPatterns\More\ServiceLocator;interfaceLogServiceInterface{}

LogService.php

1234567 <?phpnamespaceDesignPatterns\More\ServiceLocator;classLogServiceimplementsLogServiceInterface{}

DatabaseServiceInterface.php

1234567 <?phpnamespaceDesignPatterns\More\ServiceLocator;interfaceDatabaseServiceInterface{}

DatabaseService.php

1234567 <?phpnamespaceDesignPatterns\More\ServiceLocator;classDatabaseServiceimplementsDatabaseServiceInterface{}

4、測試程式碼

Tests/ServiceLocatorTest.php

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667